Clean push: Removed heavy files & added only latest snapshot
This commit is contained in:
558
qt_app_pyside1/finale/main_window.py
Normal file
558
qt_app_pyside1/finale/main_window.py
Normal file
@@ -0,0 +1,558 @@
|
||||
"""
|
||||
Finale UI - Modern Main Window
|
||||
Advanced traffic monitoring interface with Material Design and dark theme.
|
||||
Connects to existing detection/violation logic from qt_app_pyside.
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget,
|
||||
QDockWidget, QSplitter, QFrame, QMessageBox, QApplication,
|
||||
QFileDialog, QStatusBar, QMenuBar, QMenu, QToolBar
|
||||
)
|
||||
from PySide6.QtCore import Qt, QTimer, QSettings, QSize, Signal, Slot, QPropertyAnimation, QEasingCurve
|
||||
from PySide6.QtGui import QIcon, QPixmap, QAction, QPainter, QBrush, QColor
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
# Import finale UI components
|
||||
try:
|
||||
# Try relative imports first (when running as a package)
|
||||
from .styles import FinaleStyles, MaterialColors
|
||||
from .icons import FinaleIcons
|
||||
from .toolbar import FinaleToolbar
|
||||
from .components.stats_widgets import StatsWidget, MetricsWidget, SystemResourceWidget
|
||||
from .views import LiveView, AnalyticsView, ViolationsView, SettingsView
|
||||
except ImportError:
|
||||
# Fallback to direct imports (when running as script)
|
||||
try:
|
||||
from styles import FinaleStyles, MaterialColors
|
||||
from icons import FinaleIcons
|
||||
from toolbar import FinaleToolbar
|
||||
from components.stats_widgets import StatsWidget, MetricsWidget, SystemResourceWidget
|
||||
from views import LiveView, AnalyticsView, ViolationsView, SettingsView
|
||||
except ImportError:
|
||||
print('Error importing main window components')
|
||||
|
||||
# Import existing detection/violation logic from qt_app_pyside
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
try:
|
||||
from controllers.model_manager import ModelManager
|
||||
from controllers.video_controller_new import VideoController
|
||||
from controllers.analytics_controller import AnalyticsController
|
||||
from controllers.performance_overlay import PerformanceOverlay
|
||||
# Import detection_openvino for advanced detection logic
|
||||
from detection_openvino import OpenVINOVehicleDetector
|
||||
from red_light_violation_pipeline import RedLightViolationPipeline
|
||||
from utils.helpers import load_configuration, save_configuration
|
||||
from utils.annotation_utils import draw_detections, convert_cv_to_pixmap
|
||||
from utils.enhanced_annotation_utils import enhanced_draw_detections
|
||||
from utils.traffic_light_utils import detect_traffic_light_color
|
||||
except ImportError as e:
|
||||
print(f"Warning: Could not import some dependencies: {e}")
|
||||
# Fallback imports
|
||||
from controllers.model_manager import ModelManager
|
||||
VideoController = None
|
||||
def load_configuration(path): return {}
|
||||
def save_configuration(config, path): pass
|
||||
|
||||
class FinaleMainWindow(QMainWindow):
|
||||
"""
|
||||
Modern main window for traffic monitoring with advanced UI.
|
||||
Connects to existing detection/violation logic without modifying it.
|
||||
"""
|
||||
|
||||
# Signals for UI updates
|
||||
theme_changed = Signal(bool) # dark_mode
|
||||
view_changed = Signal(str) # view_name
|
||||
fullscreen_toggled = Signal(bool)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# Initialize settings and configuration
|
||||
self.settings = QSettings("Finale", "TrafficMonitoring")
|
||||
self.config_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), "qt_app_pyside", "config.json")
|
||||
self.config = load_configuration(self.config_file)
|
||||
|
||||
# UI state
|
||||
self.dark_mode = True
|
||||
self.current_view = "live"
|
||||
self.is_fullscreen = False
|
||||
|
||||
# Animation system
|
||||
self.animations = {}
|
||||
|
||||
# Initialize UI
|
||||
self.setup_ui()
|
||||
|
||||
# Initialize backend controllers (existing logic)
|
||||
self.setup_controllers()
|
||||
|
||||
# Connect signals
|
||||
self.connect_signals()
|
||||
|
||||
# Apply theme and restore settings
|
||||
self.apply_theme()
|
||||
self.restore_settings()
|
||||
|
||||
# Show ready message
|
||||
self.statusBar().showMessage("Finale UI Ready", 3000)
|
||||
|
||||
def setup_ui(self):
|
||||
"""Set up the modern user interface"""
|
||||
# Window properties with advanced styling
|
||||
self.setWindowTitle("Finale Traffic Monitoring System")
|
||||
self.setMinimumSize(1400, 900)
|
||||
self.resize(1600, 1000)
|
||||
|
||||
# Set window icon
|
||||
self.setWindowIcon(FinaleIcons.get_icon("traffic_monitoring"))
|
||||
|
||||
# Create central widget with modern layout
|
||||
self.setup_central_widget()
|
||||
|
||||
# Create modern toolbar
|
||||
self.setup_toolbar()
|
||||
|
||||
# Create docked widgets
|
||||
self.setup_dock_widgets()
|
||||
|
||||
# Create status bar
|
||||
self.setup_status_bar()
|
||||
|
||||
# Create menu bar
|
||||
self.setup_menu_bar()
|
||||
|
||||
# Apply initial styling
|
||||
self.setStyleSheet(FinaleStyles.get_main_window_style())
|
||||
|
||||
def setup_central_widget(self):
|
||||
"""Create the central widget with modern tabbed interface"""
|
||||
# Create main splitter for flexible layout
|
||||
self.main_splitter = QSplitter(Qt.Horizontal)
|
||||
|
||||
# Create left panel for main content
|
||||
self.content_widget = QWidget()
|
||||
self.content_layout = QVBoxLayout(self.content_widget)
|
||||
self.content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.content_layout.setSpacing(0)
|
||||
|
||||
# Create modern tab widget
|
||||
self.tabs = QTabWidget()
|
||||
self.tabs.setTabPosition(QTabWidget.North)
|
||||
self.tabs.setMovable(True)
|
||||
self.tabs.setTabsClosable(False)
|
||||
|
||||
# Create views (these will be implemented next)
|
||||
self.live_view = LiveView()
|
||||
self.analytics_view = AnalyticsView()
|
||||
self.violations_view = ViolationsView()
|
||||
self.settings_view = SettingsView()
|
||||
|
||||
# Add tabs with icons
|
||||
self.tabs.addTab(self.live_view, FinaleIcons.get_icon("live"), "Live Detection")
|
||||
self.tabs.addTab(self.analytics_view, FinaleIcons.get_icon("analytics"), "Analytics")
|
||||
self.tabs.addTab(self.violations_view, FinaleIcons.get_icon("warning"), "Violations")
|
||||
self.tabs.addTab(self.settings_view, FinaleIcons.get_icon("settings"), "Settings")
|
||||
|
||||
# Style the tab widget
|
||||
self.tabs.setStyleSheet(FinaleStyles.get_tab_widget_style())
|
||||
|
||||
# Add to layout
|
||||
self.content_layout.addWidget(self.tabs)
|
||||
self.main_splitter.addWidget(self.content_widget)
|
||||
|
||||
# Set as central widget
|
||||
self.setCentralWidget(self.main_splitter)
|
||||
|
||||
def setup_toolbar(self):
|
||||
"""Create the modern toolbar"""
|
||||
self.toolbar = FinaleToolbar(self)
|
||||
self.addToolBar(Qt.TopToolBarArea, self.toolbar)
|
||||
|
||||
# Connect toolbar signals
|
||||
self.toolbar.play_clicked.connect(self.on_play_clicked)
|
||||
self.toolbar.pause_clicked.connect(self.on_pause_clicked)
|
||||
self.toolbar.stop_clicked.connect(self.on_stop_clicked)
|
||||
self.toolbar.record_clicked.connect(self.on_record_clicked)
|
||||
self.toolbar.snapshot_clicked.connect(self.on_snapshot_clicked)
|
||||
self.toolbar.settings_clicked.connect(self.show_settings)
|
||||
self.toolbar.fullscreen_clicked.connect(self.toggle_fullscreen)
|
||||
self.toolbar.theme_changed.connect(self.set_dark_mode)
|
||||
|
||||
def setup_dock_widgets(self):
|
||||
"""Create docked widgets for statistics and controls"""
|
||||
# Stats dock widget
|
||||
self.stats_dock = QDockWidget("Statistics", self)
|
||||
self.stats_dock.setObjectName("StatsDock")
|
||||
self.stats_widget = StatsWidget()
|
||||
self.stats_dock.setWidget(self.stats_widget)
|
||||
self.stats_dock.setFeatures(
|
||||
QDockWidget.DockWidgetMovable |
|
||||
QDockWidget.DockWidgetClosable |
|
||||
QDockWidget.DockWidgetFloatable
|
||||
)
|
||||
self.addDockWidget(Qt.RightDockWidgetArea, self.stats_dock)
|
||||
|
||||
# Metrics dock widget
|
||||
self.metrics_dock = QDockWidget("Performance", self)
|
||||
self.metrics_dock.setObjectName("MetricsDock")
|
||||
self.metrics_widget = MetricsWidget()
|
||||
self.metrics_dock.setWidget(self.metrics_widget)
|
||||
self.metrics_dock.setFeatures(
|
||||
QDockWidget.DockWidgetMovable |
|
||||
QDockWidget.DockWidgetClosable |
|
||||
QDockWidget.DockWidgetFloatable
|
||||
)
|
||||
self.addDockWidget(Qt.RightDockWidgetArea, self.metrics_dock)
|
||||
|
||||
# System resources dock widget
|
||||
self.system_dock = QDockWidget("System", self)
|
||||
self.system_dock.setObjectName("SystemDock")
|
||||
self.system_widget = SystemResourceWidget()
|
||||
self.system_dock.setWidget(self.system_widget)
|
||||
self.system_dock.setFeatures(
|
||||
QDockWidget.DockWidgetMovable |
|
||||
QDockWidget.DockWidgetClosable |
|
||||
QDockWidget.DockWidgetFloatable
|
||||
)
|
||||
self.addDockWidget(Qt.RightDockWidgetArea, self.system_dock)
|
||||
|
||||
# Tabify dock widgets for space efficiency
|
||||
self.tabifyDockWidget(self.stats_dock, self.metrics_dock)
|
||||
self.tabifyDockWidget(self.metrics_dock, self.system_dock)
|
||||
|
||||
# Show stats dock by default
|
||||
self.stats_dock.raise_()
|
||||
|
||||
# Apply dock widget styling
|
||||
for dock in [self.stats_dock, self.metrics_dock, self.system_dock]:
|
||||
dock.setStyleSheet(FinaleStyles.get_dock_widget_style())
|
||||
|
||||
def setup_status_bar(self):
|
||||
"""Create modern status bar"""
|
||||
self.status_bar = QStatusBar()
|
||||
self.setStatusBar(self.status_bar)
|
||||
|
||||
# Add permanent widgets to status bar
|
||||
self.fps_label = QWidget()
|
||||
self.connection_label = QWidget()
|
||||
self.model_label = QWidget()
|
||||
|
||||
self.status_bar.addPermanentWidget(self.fps_label)
|
||||
self.status_bar.addPermanentWidget(self.connection_label)
|
||||
self.status_bar.addPermanentWidget(self.model_label)
|
||||
|
||||
# Style status bar
|
||||
self.status_bar.setStyleSheet(FinaleStyles.get_status_bar_style())
|
||||
|
||||
def setup_menu_bar(self):
|
||||
"""Create modern menu bar"""
|
||||
self.menu_bar = self.menuBar()
|
||||
|
||||
# File menu
|
||||
file_menu = self.menu_bar.addMenu("&File")
|
||||
|
||||
open_action = QAction(FinaleIcons.get_icon("folder"), "&Open Video", self)
|
||||
open_action.setShortcut("Ctrl+O")
|
||||
open_action.triggered.connect(self.open_file)
|
||||
file_menu.addAction(open_action)
|
||||
|
||||
save_action = QAction(FinaleIcons.get_icon("save"), "&Save Config", self)
|
||||
save_action.setShortcut("Ctrl+S")
|
||||
save_action.triggered.connect(self.save_config)
|
||||
file_menu.addAction(save_action)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
exit_action = QAction(FinaleIcons.get_icon("exit"), "E&xit", self)
|
||||
exit_action.setShortcut("Ctrl+Q")
|
||||
exit_action.triggered.connect(self.close)
|
||||
file_menu.addAction(exit_action)
|
||||
|
||||
# View menu
|
||||
view_menu = self.menu_bar.addMenu("&View")
|
||||
|
||||
fullscreen_action = QAction(FinaleIcons.get_icon("fullscreen"), "&Fullscreen", self)
|
||||
fullscreen_action.setShortcut("F11")
|
||||
fullscreen_action.setCheckable(True)
|
||||
fullscreen_action.triggered.connect(self.toggle_fullscreen)
|
||||
view_menu.addAction(fullscreen_action)
|
||||
|
||||
theme_action = QAction(FinaleIcons.get_icon("theme"), "&Dark Theme", self)
|
||||
theme_action.setCheckable(True)
|
||||
theme_action.setChecked(self.dark_mode)
|
||||
theme_action.triggered.connect(self.toggle_theme)
|
||||
view_menu.addAction(theme_action)
|
||||
|
||||
# Tools menu
|
||||
tools_menu = self.menu_bar.addMenu("&Tools")
|
||||
|
||||
settings_action = QAction(FinaleIcons.get_icon("settings"), "&Settings", self)
|
||||
settings_action.setShortcut("Ctrl+,")
|
||||
settings_action.triggered.connect(self.show_settings)
|
||||
tools_menu.addAction(settings_action)
|
||||
|
||||
# Help menu
|
||||
help_menu = self.menu_bar.addMenu("&Help")
|
||||
|
||||
about_action = QAction(FinaleIcons.get_icon("info"), "&About", self)
|
||||
about_action.triggered.connect(self.show_about)
|
||||
help_menu.addAction(about_action)
|
||||
|
||||
# Style menu bar
|
||||
self.menu_bar.setStyleSheet(FinaleStyles.get_menu_bar_style())
|
||||
|
||||
def setup_controllers(self):
|
||||
"""Initialize backend controllers (existing logic)"""
|
||||
try:
|
||||
# Initialize model manager (existing from qt_app_pyside)
|
||||
self.model_manager = ModelManager(self.config_file)
|
||||
|
||||
# Initialize video controller (existing from qt_app_pyside)
|
||||
self.video_controller = VideoController(self.model_manager)
|
||||
|
||||
# Initialize analytics controller (existing from qt_app_pyside)
|
||||
self.analytics_controller = AnalyticsController()
|
||||
|
||||
# Initialize performance overlay (existing from qt_app_pyside)
|
||||
self.performance_overlay = PerformanceOverlay()
|
||||
|
||||
print("✅ Backend controllers initialized successfully")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error initializing controllers: {e}")
|
||||
QMessageBox.critical(self, "Initialization Error",
|
||||
f"Failed to initialize backend controllers:\n{str(e)}")
|
||||
|
||||
def connect_signals(self):
|
||||
"""Connect signals between UI and backend"""
|
||||
try:
|
||||
# Connect video controller signals to UI updates
|
||||
if hasattr(self.video_controller, 'frame_ready'):
|
||||
self.video_controller.frame_ready.connect(self.on_frame_ready)
|
||||
|
||||
if hasattr(self.video_controller, 'stats_ready'):
|
||||
self.video_controller.stats_ready.connect(self.on_stats_ready)
|
||||
|
||||
if hasattr(self.video_controller, 'violation_detected'):
|
||||
self.video_controller.violation_detected.connect(self.on_violation_detected)
|
||||
|
||||
# Connect tab change signal
|
||||
self.tabs.currentChanged.connect(self.on_tab_changed)
|
||||
|
||||
# Connect view signals to backend
|
||||
self.live_view.source_changed.connect(self.on_source_changed)
|
||||
|
||||
print("✅ Signals connected successfully")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error connecting signals: {e}")
|
||||
|
||||
# Event handlers for UI interactions
|
||||
@Slot()
|
||||
def on_play_clicked(self):
|
||||
"""Handle play button click"""
|
||||
if hasattr(self.video_controller, 'start'):
|
||||
self.video_controller.start()
|
||||
self.toolbar.set_playback_state("playing")
|
||||
|
||||
@Slot()
|
||||
def on_pause_clicked(self):
|
||||
"""Handle pause button click"""
|
||||
if hasattr(self.video_controller, 'pause'):
|
||||
self.video_controller.pause()
|
||||
self.toolbar.set_playback_state("paused")
|
||||
|
||||
@Slot()
|
||||
def on_stop_clicked(self):
|
||||
"""Handle stop button click"""
|
||||
if hasattr(self.video_controller, 'stop'):
|
||||
self.video_controller.stop()
|
||||
self.toolbar.set_playback_state("stopped")
|
||||
|
||||
@Slot()
|
||||
def on_record_clicked(self):
|
||||
"""Handle record button click"""
|
||||
# Implementation depends on existing recording logic
|
||||
pass
|
||||
|
||||
@Slot()
|
||||
def on_snapshot_clicked(self):
|
||||
"""Handle snapshot button click"""
|
||||
# Implementation depends on existing snapshot logic
|
||||
pass
|
||||
|
||||
# Backend signal handlers
|
||||
@Slot(object, object, dict)
|
||||
def on_frame_ready(self, pixmap, detections, metrics):
|
||||
"""Handle frame ready signal from video controller"""
|
||||
# Update live view
|
||||
if self.current_view == "live":
|
||||
self.live_view.update_frame(pixmap, detections)
|
||||
|
||||
# Update toolbar status
|
||||
self.toolbar.update_status("processing", True)
|
||||
|
||||
@Slot(dict)
|
||||
def on_stats_ready(self, stats):
|
||||
"""Handle stats ready signal from video controller"""
|
||||
# Update stats widgets
|
||||
self.stats_widget.update_stats(stats)
|
||||
self.metrics_widget.update_metrics(stats)
|
||||
|
||||
# Update toolbar FPS
|
||||
if 'fps' in stats:
|
||||
self.toolbar.update_fps(stats['fps'])
|
||||
|
||||
@Slot(dict)
|
||||
def on_violation_detected(self, violation_data):
|
||||
"""Handle violation detected signal"""
|
||||
# Update violations view
|
||||
self.violations_view.add_violation(violation_data)
|
||||
|
||||
# Update toolbar status
|
||||
self.toolbar.update_status("violation", True)
|
||||
|
||||
# Play notification sound/animation if enabled
|
||||
self.play_violation_notification()
|
||||
|
||||
@Slot(str)
|
||||
def on_source_changed(self, source_path):
|
||||
"""Handle source change from live view"""
|
||||
if hasattr(self.video_controller, 'set_source'):
|
||||
self.video_controller.set_source(source_path)
|
||||
|
||||
@Slot(int)
|
||||
def on_tab_changed(self, index):
|
||||
"""Handle tab change"""
|
||||
tab_names = ["live", "analytics", "violations", "settings"]
|
||||
if 0 <= index < len(tab_names):
|
||||
self.current_view = tab_names[index]
|
||||
self.view_changed.emit(self.current_view)
|
||||
|
||||
# UI control methods
|
||||
def toggle_fullscreen(self):
|
||||
"""Toggle fullscreen mode"""
|
||||
if self.isFullScreen():
|
||||
self.showNormal()
|
||||
self.is_fullscreen = False
|
||||
else:
|
||||
self.showFullScreen()
|
||||
self.is_fullscreen = True
|
||||
|
||||
self.fullscreen_toggled.emit(self.is_fullscreen)
|
||||
|
||||
def toggle_theme(self):
|
||||
"""Toggle between dark and light theme"""
|
||||
self.set_dark_mode(not self.dark_mode)
|
||||
|
||||
def set_dark_mode(self, dark_mode):
|
||||
"""Set theme mode"""
|
||||
self.dark_mode = dark_mode
|
||||
self.apply_theme()
|
||||
self.theme_changed.emit(self.dark_mode)
|
||||
|
||||
def apply_theme(self):
|
||||
"""Apply current theme to all UI elements"""
|
||||
# Apply main styles
|
||||
self.setStyleSheet(FinaleStyles.get_main_window_style(self.dark_mode))
|
||||
|
||||
# Update all child widgets
|
||||
for child in self.findChildren(QWidget):
|
||||
if hasattr(child, 'apply_theme'):
|
||||
child.apply_theme(self.dark_mode)
|
||||
|
||||
# Update color scheme
|
||||
if self.dark_mode:
|
||||
MaterialColors.apply_dark_theme()
|
||||
else:
|
||||
MaterialColors.apply_light_theme()
|
||||
|
||||
def show_settings(self):
|
||||
"""Show settings view"""
|
||||
self.tabs.setCurrentWidget(self.settings_view)
|
||||
|
||||
def show_about(self):
|
||||
"""Show about dialog"""
|
||||
QMessageBox.about(self, "About Finale UI",
|
||||
"Finale Traffic Monitoring System\n"
|
||||
"Modern UI for OpenVINO-based traffic detection\n"
|
||||
"Built with PySide6 and Material Design")
|
||||
|
||||
def open_file(self):
|
||||
"""Open file dialog for video source"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "Open Video File", "",
|
||||
"Video Files (*.mp4 *.avi *.mov *.mkv);;All Files (*)"
|
||||
)
|
||||
if file_path:
|
||||
self.on_source_changed(file_path)
|
||||
|
||||
def save_config(self):
|
||||
"""Save current configuration"""
|
||||
try:
|
||||
save_configuration(self.config, self.config_file)
|
||||
self.statusBar().showMessage("Configuration saved", 3000)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Save Error", f"Failed to save configuration:\n{str(e)}")
|
||||
|
||||
def play_violation_notification(self):
|
||||
"""Play violation notification (visual/audio)"""
|
||||
# Create a brief red flash animation
|
||||
self.create_violation_flash()
|
||||
|
||||
def create_violation_flash(self):
|
||||
"""Create a red flash effect for violations"""
|
||||
# Create a semi-transparent red overlay
|
||||
overlay = QWidget(self)
|
||||
overlay.setStyleSheet("background-color: rgba(244, 67, 54, 0.3);")
|
||||
overlay.resize(self.size())
|
||||
overlay.show()
|
||||
|
||||
# Animate the overlay
|
||||
self.flash_animation = QPropertyAnimation(overlay, b"windowOpacity")
|
||||
self.flash_animation.setDuration(500)
|
||||
self.flash_animation.setStartValue(0.3)
|
||||
self.flash_animation.setEndValue(0.0)
|
||||
self.flash_animation.setEasingCurve(QEasingCurve.OutCubic)
|
||||
self.flash_animation.finished.connect(overlay.deleteLater)
|
||||
self.flash_animation.start()
|
||||
|
||||
# Settings persistence
|
||||
def save_settings(self):
|
||||
"""Save window settings"""
|
||||
self.settings.setValue("geometry", self.saveGeometry())
|
||||
self.settings.setValue("windowState", self.saveState())
|
||||
self.settings.setValue("dark_mode", self.dark_mode)
|
||||
self.settings.setValue("current_view", self.current_view)
|
||||
|
||||
def restore_settings(self):
|
||||
"""Restore window settings"""
|
||||
if self.settings.contains("geometry"):
|
||||
self.restoreGeometry(self.settings.value("geometry"))
|
||||
if self.settings.contains("windowState"):
|
||||
self.restoreState(self.settings.value("windowState"))
|
||||
if self.settings.contains("dark_mode"):
|
||||
self.dark_mode = self.settings.value("dark_mode", True, bool)
|
||||
if self.settings.contains("current_view"):
|
||||
view_name = self.settings.value("current_view", "live")
|
||||
view_index = {"live": 0, "analytics": 1, "violations": 2, "settings": 3}.get(view_name, 0)
|
||||
self.tabs.setCurrentIndex(view_index)
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Handle window close event"""
|
||||
# Save settings
|
||||
self.save_settings()
|
||||
|
||||
# Stop video controller
|
||||
if hasattr(self.video_controller, 'stop'):
|
||||
self.video_controller.stop()
|
||||
|
||||
# Accept close event
|
||||
event.accept()
|
||||
Reference in New Issue
Block a user