#!/usr/bin/env python3 from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSizePolicy, QCheckBox) from PyQt6.QtCore import pyqtSignal class DronePanel(QWidget): """單個無人機面板類別""" # 定義信號 mode_change_requested = pyqtSignal(str) # drone_id arm_requested = pyqtSignal(str) # drone_id takeoff_requested = pyqtSignal(str) # drone_id setpoint_requested = pyqtSignal(str) # drone_id selection_changed = pyqtSignal(str, int) # drone_id, state def __init__(self, drone_id, parent=None): super().__init__(parent) self.drone_id = drone_id self.display_id = 's' + drone_id.split('_')[1] self._init_ui() def _init_ui(self): """初始化UI""" self.setObjectName(f"panel_{self.drone_id}") self.setFixedHeight(140) self.setStyleSheet(""" background-color: #2A2A2A; border-radius: 8px; """) # 主佈局 main_layout = QHBoxLayout(self) main_layout.setContentsMargins(8, 8, 8, 8) main_layout.setSpacing(0) # 創建內容容器(包含 info 和 control) content_widget = QWidget() content_widget.setStyleSheet("background-color: #333; border-radius: 6px;") content_layout = QHBoxLayout(content_widget) content_layout.setContentsMargins(8, 8, 8, 8) content_layout.setSpacing(8) # 左側資訊區域 info_widget = self._create_info_section() # 右側控制按鈕區域 control_widget = self._create_control_section() # 將 info 和 control 加入內容容器 content_layout.addWidget(info_widget) content_layout.addWidget(control_widget) # 將內容容器加入主佈局 main_layout.addWidget(content_widget) def _create_info_section(self): """創建資訊顯示區域""" info_widget = QWidget() info_layout = QVBoxLayout(info_widget) info_layout.setContentsMargins(0, 0, 0, 0) info_layout.setSpacing(4) # 頂部標題欄 header = QWidget() header_layout = QHBoxLayout(header) header_layout.setContentsMargins(0, 0, 0, 0) # 勾選框 self.checkbox = QCheckBox() self.checkbox.setObjectName(f"{self.drone_id}_checkbox") self.checkbox.setStyleSheet("QCheckBox { color: #DDD; }") self.checkbox.stateChanged.connect( lambda state: self.selection_changed.emit(self.drone_id, state) ) # ID 顯示 id_label = QLabel(self.display_id) id_label.setStyleSheet(""" font-weight: bold; font-size: 14px; color: #7FFFD4; min-width: 80px; """) header_layout.addWidget(self.checkbox) header_layout.addWidget(id_label) header_layout.addStretch() info_layout.addWidget(header) # 第一行:狀態 (模式 + ARM狀態) status_row = self._create_status_row() info_layout.addWidget(status_row) # 第二行:電池 battery_row = self._create_battery_row() info_layout.addWidget(battery_row) # 第三行:位置 + 高度 position_row = self._create_position_row() info_layout.addWidget(position_row) # 第四行:航向 + 速度 nav_row = self._create_nav_row() info_layout.addWidget(nav_row) return info_widget def _create_status_row(self): """創建狀態行""" status_row = QWidget() status_layout = QHBoxLayout(status_row) status_layout.setContentsMargins(0, 0, 0, 0) status_title = QLabel("狀態:") status_title.setStyleSheet("color: #888; min-width: 50px;") self.mode_label = QLabel("--") self.mode_label.setObjectName(f"{self.drone_id}_mode") self.mode_label.setStyleSheet("color: #DDD;") self.armed_label = QLabel("--") self.armed_label.setObjectName(f"{self.drone_id}_armed") self.armed_label.setStyleSheet("color: #DDD;") status_layout.addWidget(status_title) status_layout.addWidget(self.mode_label) status_layout.addWidget(self.armed_label) status_layout.addStretch() return status_row def _create_battery_row(self): """創建電池行""" battery_row = QWidget() battery_layout = QHBoxLayout(battery_row) battery_layout.setContentsMargins(0, 0, 0, 0) battery_title = QLabel("電池:") battery_title.setStyleSheet("color: #888; min-width: 50px;") self.battery_label = QLabel("--") self.battery_label.setObjectName(f"{self.drone_id}_battery") self.battery_label.setStyleSheet("color: #DDD;") battery_layout.addWidget(battery_title) battery_layout.addWidget(self.battery_label) battery_layout.addStretch() return battery_row def _create_position_row(self): """創建位置行""" position_row = QWidget() position_layout = QHBoxLayout(position_row) position_layout.setContentsMargins(0, 0, 0, 0) position_title = QLabel("位置:") position_title.setStyleSheet("color: #888; min-width: 50px;") self.local_label = QLabel("--") self.local_label.setObjectName(f"{self.drone_id}_local") self.local_label.setStyleSheet("color: #DDD;") altitude_title = QLabel("高度:") altitude_title.setStyleSheet("color: #888; margin-left: 10px;") self.altitude_label = QLabel("--") self.altitude_label.setObjectName(f"{self.drone_id}_altitude") self.altitude_label.setStyleSheet("color: #DDD;") position_layout.addWidget(position_title) position_layout.addWidget(self.local_label) position_layout.addWidget(altitude_title) position_layout.addWidget(self.altitude_label) position_layout.addStretch() return position_row def _create_nav_row(self): """創建導航行""" nav_row = QWidget() nav_layout = QHBoxLayout(nav_row) nav_layout.setContentsMargins(0, 0, 0, 0) heading_title = QLabel("航向:") heading_title.setStyleSheet("color: #888; min-width: 50px;") self.heading_label = QLabel("--") self.heading_label.setObjectName(f"{self.drone_id}_heading") self.heading_label.setStyleSheet("color: #DDD;") speed_title = QLabel("速度:") speed_title.setStyleSheet("color: #888; margin-left: 10px;") self.groundspeed_label = QLabel("--") self.groundspeed_label.setObjectName(f"{self.drone_id}_groundspeed") self.groundspeed_label.setStyleSheet("color: #DDD;") nav_layout.addWidget(heading_title) nav_layout.addWidget(self.heading_label) nav_layout.addWidget(speed_title) nav_layout.addWidget(self.groundspeed_label) nav_layout.addStretch() return nav_row def _create_control_section(self): """創建控制按鈕區域""" control_widget = QWidget() control_layout = QVBoxLayout(control_widget) control_layout.setContentsMargins(0, 0, 0, 0) control_layout.setSpacing(6) control_widget.setFixedWidth(80) btn_style = """ QPushButton { background-color: #444; color: #DDD; border: none; border-radius: 4px; font-size: 11px; } QPushButton:hover { background-color: #555; } """ # 模式切換按鈕 mode_btn = QPushButton("切換模式") mode_btn.setStyleSheet(btn_style) mode_btn.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) mode_btn.clicked.connect(lambda: self.mode_change_requested.emit(self.drone_id)) # 解鎖按鈕 arm_btn = QPushButton("解鎖") arm_btn.setStyleSheet(btn_style) arm_btn.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) arm_btn.clicked.connect(lambda: self.arm_requested.emit(self.drone_id)) # 起飛按鈕 takeoff_btn = QPushButton("起飛") takeoff_btn.setStyleSheet(btn_style) takeoff_btn.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) takeoff_btn.clicked.connect(lambda: self.takeoff_requested.emit(self.drone_id)) # Setpoint 按鈕 setpoint_btn = QPushButton("Setpoint") setpoint_btn.setStyleSheet(btn_style) setpoint_btn.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) setpoint_btn.clicked.connect(lambda: self.setpoint_requested.emit(self.drone_id)) control_layout.addWidget(mode_btn) control_layout.addWidget(arm_btn) control_layout.addWidget(takeoff_btn) control_layout.addWidget(setpoint_btn) return control_widget def update_field(self, field, text, color=None): """更新指定欄位的值""" label = self.findChild(QLabel, f"{self.drone_id}_{field}") if label and label.text() != text: label.setText(text) if color: label.setStyleSheet(f"color: {color};") def get_checkbox(self): """獲取勾選框""" return self.checkbox def set_checked(self, checked): """設置勾選狀態""" self.checkbox.setChecked(checked) def is_checked(self): """獲取勾選狀態""" return self.checkbox.isChecked() class SocketGroupPanel(QWidget): # 定義信號 group_selection_changed = pyqtSignal(str, int) # socket_id, state def __init__(self, socket_id, color='#AAAAAA', parent=None): super().__init__(parent) self.socket_id = socket_id self.color = color self._init_ui() def _init_ui(self): """初始化UI""" self.setObjectName(f"socket_group_{self.socket_id}") self.setStyleSheet(""" background-color: #1E1E1E; border-radius: 12px; """) layout = QVBoxLayout(self) layout.setContentsMargins(10, 10, 10, 10) layout.setSpacing(6) # Socket 分組標題行 - 包含勾選框 title_row = QWidget() title_layout = QHBoxLayout(title_row) title_layout.setContentsMargins(0, 0, 0, 0) # 分組勾選框 self.group_checkbox = QCheckBox() self.group_checkbox.setObjectName(f"socket_{self.socket_id}_checkbox") self.group_checkbox.setStyleSheet(f""" QCheckBox {{ color: #DDD; }} QCheckBox::indicator {{ width: 14px; height: 14px; border: 2px solid #888888; border-radius: 3px; background: transparent; }} QCheckBox::indicator:checked {{ background-color: {self.color}; border: 2px solid #888888; }} QCheckBox::indicator:indeterminate {{ background-color: #666; border: 2px solid #888888; }} """) self.group_checkbox.stateChanged.connect( lambda state: self.group_selection_changed.emit(self.socket_id, state) ) # Socket 分組標題 title_label = QLabel(f"Socket {self.socket_id}") title_label.setStyleSheet(f""" font-weight: bold; font-size: 16px; color: {self.color}; margin-bottom: 8px; padding: 4px 8px; border-radius: 6px; """) title_layout.addWidget(self.group_checkbox) title_layout.addWidget(title_label) title_layout.addStretch() layout.addWidget(title_row) # 創建子容器用於放置該 socket 下的所有無人機面板 self.drones_container = QWidget() self.drones_layout = QVBoxLayout(self.drones_container) self.drones_layout.setContentsMargins(0, 0, 0, 0) self.drones_layout.setSpacing(4) layout.addWidget(self.drones_container) def add_drone_panel(self, panel): """添加無人機面板到分組""" self.drones_layout.addWidget(panel) def clear_drones(self): """清空所有無人機面板""" while self.drones_layout.count(): item = self.drones_layout.takeAt(0) if item.widget(): item.widget().setParent(None) def get_checkbox(self): """獲取分組勾選框""" return self.group_checkbox def set_checked(self, checked): """設置分組勾選狀態""" self.group_checkbox.setChecked(checked) def set_check_state(self, state): """設置分組勾選狀態(支持半選)""" self.group_checkbox.setCheckState(state)