import os import tempfile import cv2 from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QRadioButton, QButtonGroup, QFileDialog, QTextEdit, QGroupBox, QFrame, QScrollArea) from PyQt5.QtCore import Qt, pyqtSignal, QTimer from PyQt5.QtGui import QFont, QPalette class ControlPanelWidget(QScrollArea): video_selected = pyqtSignal(str) start_comparison = pyqtSignal(str) stop_comparison = pyqtSignal() initialize_system = pyqtSignal() def __init__(self, motion_app): super().__init__() self.motion_app = motion_app self.current_video_path = None self.setup_ui() def setup_ui(self): # Create main widget for scroll area main_widget = QWidget() main_layout = QVBoxLayout(main_widget) main_layout.setSpacing(10) main_layout.setContentsMargins(15, 15, 15, 15) # Title title_label = QLabel("🎛️ 控制面板") title_font = QFont() title_font.setPointSize(14) title_font.setBold(True) title_label.setFont(title_font) main_layout.addWidget(title_label) # Add separator separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setFrameShadow(QFrame.Sunken) main_layout.addWidget(separator) # Display settings group self.setup_display_settings(main_layout) # Video source selection group self.setup_video_source(main_layout) # System initialization group self.setup_system_init(main_layout) # System status group self.setup_system_status(main_layout) # Stretch to push everything to top main_layout.addStretch() # Set scroll area properties self.setWidget(main_widget) self.setWidgetResizable(True) self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # Style the scroll area self.setStyleSheet(""" QScrollArea { border: none; background-color: #ffffff; } QGroupBox { font-weight: bold; border: 2px solid #cccccc; border-radius: 5px; margin-top: 10px; padding-top: 10px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 10px 0 10px; } """) def setup_display_settings(self, parent_layout): group = QGroupBox("显示设置") layout = QVBoxLayout(group) # Resolution selection res_label = QLabel("显示分辨率:") self.resolution_combo = QComboBox() self.resolution_combo.addItems([ "高清 (1280x800)", "中等 (960x720)", "标准 (640x480)" ]) self.resolution_combo.setCurrentIndex(1) # Default to medium self.resolution_combo.currentTextChanged.connect(self.on_resolution_changed) layout.addWidget(res_label) layout.addWidget(self.resolution_combo) parent_layout.addWidget(group) def setup_video_source(self, parent_layout): group = QGroupBox("视频来源") layout = QVBoxLayout(group) # Radio buttons for video source self.video_source_group = QButtonGroup() self.preset_radio = QRadioButton("预设视频") self.upload_radio = QRadioButton("上传视频") self.preset_radio.setChecked(True) self.video_source_group.addButton(self.preset_radio, 0) self.video_source_group.addButton(self.upload_radio, 1) layout.addWidget(self.preset_radio) layout.addWidget(self.upload_radio) # Preset video selection self.preset_combo = QComboBox() # --- FIX STARTS HERE --- # The incorrect call to self.load_preset_videos() has been removed from here. # --- FIX ENDS HERE --- # Upload button self.upload_button = QPushButton("选择文件...") self.upload_button.clicked.connect(self.upload_video) self.upload_button.setEnabled(False) # Video info display - This is now created BEFORE it is used. self.video_info = QLabel("未选择视频") self.video_info.setWordWrap(True) self.video_info.setStyleSheet("color: #666; font-size: 9pt;") layout.addWidget(self.preset_combo) layout.addWidget(self.upload_button) layout.addWidget(self.video_info) # Connect signals self.preset_radio.toggled.connect(self.on_video_source_changed) self.preset_combo.currentTextChanged.connect(self.on_preset_video_changed) parent_layout.addWidget(group) # Load available presets after all UI elements are set up. This is the correct place. self.load_preset_videos() def setup_system_init(self, parent_layout): group = QGroupBox("⚙️ 系统初始化") layout = QVBoxLayout(group) self.init_button = QPushButton("🚀 初始化系统") self.init_button.clicked.connect(self.initialize_system_clicked) self.init_button.setMinimumHeight(40) self.init_button.setStyleSheet(""" QPushButton { background-color: #0086d3; color: white; border: none; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #006ba3; } QPushButton:pressed { background-color: #004d73; } """) # Control buttons button_layout = QHBoxLayout() self.preview_button = QPushButton("📷 预览") self.start_button = QPushButton("🚀 开始比较") self.preview_button.clicked.connect(self.preview_camera) self.start_button.clicked.connect(self.start_comparison_clicked) self.preview_button.setEnabled(False) self.start_button.setEnabled(False) button_layout.addWidget(self.preview_button) button_layout.addWidget(self.start_button) layout.addWidget(self.init_button) layout.addLayout(button_layout) parent_layout.addWidget(group) def setup_system_status(self, parent_layout): group = QGroupBox("ℹ️ 系统状态") layout = QVBoxLayout(group) self.status_info = QTextEdit() self.status_info.setMaximumHeight(100) self.status_info.setReadOnly(True) self.update_system_status() layout.addWidget(self.status_info) parent_layout.addWidget(group) def load_preset_videos(self): preset_videos = { "六字诀": "liuzi.mp4", "六字诀精简": "liuzi-short.mp4", } preset_folder = "preset_videos" if os.path.exists(preset_folder): available_presets = [] for display_name, filename in preset_videos.items(): full_path = os.path.join(preset_folder, filename) if os.path.exists(full_path): available_presets.append(display_name) self.preset_combo.clear() # Clear previous items before adding new ones if available_presets: self.preset_combo.addItems(available_presets) # Set default selection if available_presets: self.on_preset_video_changed(available_presets[0]) else: self.preset_combo.addItem("无可用预设视频") else: self.preset_combo.addItem("preset_videos 文件夹不存在") def on_video_source_changed(self, checked): if self.preset_radio.isChecked(): self.preset_combo.setEnabled(True) self.upload_button.setEnabled(False) if self.preset_combo.count() > 0 and self.preset_combo.currentText() != "无可用预设视频": self.on_preset_video_changed(self.preset_combo.currentText()) else: self.preset_combo.setEnabled(False) self.upload_button.setEnabled(True) self.current_video_path = None self.video_info.setText("请选择上传文件") def on_preset_video_changed(self, display_name): preset_videos = { "六字诀": "liuzi.mp4", "六字诀精简": "liuzi-short.mp4", } if display_name in preset_videos: filename = preset_videos[display_name] video_path = os.path.join("preset_videos", filename) if os.path.exists(video_path): self.current_video_path = video_path self.update_video_info() self.video_selected.emit(video_path) else: self.video_info.setText("视频文件不存在") def upload_video(self): file_path, _ = QFileDialog.getOpenFileName( self, "选择视频文件", "", "Video Files (*.mp4 *.avi *.mov *.mkv);;All Files (*)" ) if file_path: self.current_video_path = file_path self.update_video_info() self.video_selected.emit(file_path) def update_video_info(self): if self.current_video_path: try: cap = cv2.VideoCapture(self.current_video_path) if cap.isOpened(): fps = cap.get(cv2.CAP_PROP_FPS) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) duration = frame_count / fps if fps > 0 else 0 width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) info_text = f"时长: {duration:.1f}s\n帧率: {fps:.1f}\n分辨率: {width}×{height}" self.video_info.setText(info_text) cap.release() else: self.video_info.setText("无法读取视频信息") except Exception as e: self.video_info.setText(f"错误: {str(e)}") else: self.video_info.setText("未选择视频") def on_resolution_changed(self, text): resolution_map = { "高清 (1280x800)": "high", "中等 (960x720)": "medium", "标准 (640x480)": "low" } mode = resolution_map.get(text, "medium") self.motion_app.display_settings['resolution_mode'] = mode def initialize_system_clicked(self): self.init_button.setEnabled(False) self.init_button.setText("正在初始化...") # Enable this after successful initialization QTimer.singleShot(100, self.do_initialize) def do_initialize(self): success = self.motion_app.initialize_all() if success: self.init_button.setText("✅ 初始化完成") self.preview_button.setEnabled(True) if self.current_video_path: self.start_button.setEnabled(True) self.update_system_status() else: self.init_button.setText("❌ 初始化失败") self.init_button.setEnabled(True) def preview_camera(self): # Emit signal to show camera preview pass def start_comparison_clicked(self): if self.current_video_path: self.start_comparison.emit(self.current_video_path) def update_system_status(self): import torch from config import REALSENSE_AVAILABLE, PYGAME_AVAILABLE status_text = f"""计算设备: {'GPU (CUDA)' if torch.cuda.is_available() else 'CPU'} 摄像头: {'RealSense' if REALSENSE_AVAILABLE else 'USB 摄像头'} 音频: {'启用' if PYGAME_AVAILABLE else '禁用'} 检测器: {'已初始化' if self.motion_app.body_detector else '未初始化'}""" self.status_info.setText(status_text)