|
|
|
|
@ -33,7 +33,7 @@ from mission_group import (
|
|
|
|
|
# ================================================================================
|
|
|
|
|
|
|
|
|
|
class ControlStationUI(QMainWindow):
|
|
|
|
|
VERSION = '2.0.7'
|
|
|
|
|
VERSION = '2.0.8'
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
super().__init__()
|
|
|
|
|
@ -650,18 +650,25 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
"""統一刷新所有 UI 元素:左側 drone 勾選、左側 socket 勾選、右側 group panel"""
|
|
|
|
|
group = self._get_active_group()
|
|
|
|
|
selected = group.selected_drone_ids if group else set()
|
|
|
|
|
current_group_id = group.group_id if group else None
|
|
|
|
|
|
|
|
|
|
# 左側 drone checkbox
|
|
|
|
|
for drone_id, panel in self.drones.items():
|
|
|
|
|
checkbox = panel.get_checkbox()
|
|
|
|
|
if checkbox:
|
|
|
|
|
blocked = (
|
|
|
|
|
current_group_id is not None and
|
|
|
|
|
self._is_drone_selected_by_other_group(drone_id, current_group_id)
|
|
|
|
|
)
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
|
checkbox.setStyleSheet(self._get_drone_checkbox_style(blocked))
|
|
|
|
|
checkbox.setChecked(drone_id in selected)
|
|
|
|
|
checkbox.setEnabled(not blocked or drone_id in selected)
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
|
|
# 左側 socket checkbox
|
|
|
|
|
for socket_id in self.socket_groups.keys():
|
|
|
|
|
self.refresh_socket_checkbox(socket_id, selected)
|
|
|
|
|
self.refresh_socket_checkbox(socket_id, selected, current_group_id)
|
|
|
|
|
|
|
|
|
|
# 右側 group panel
|
|
|
|
|
if group:
|
|
|
|
|
@ -670,7 +677,33 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
panel.update_drone_list()
|
|
|
|
|
panel.update_status()
|
|
|
|
|
|
|
|
|
|
def refresh_socket_checkbox(self, socket_id, selected_ids):
|
|
|
|
|
def _get_drone_checkbox_style(self, blocked=False):
|
|
|
|
|
"""回傳 drone checkbox 樣式;被其他 group 佔用時顯示紅色填滿。"""
|
|
|
|
|
blocked_style = """
|
|
|
|
|
QCheckBox::indicator:disabled {
|
|
|
|
|
background-color: #D32F2F;
|
|
|
|
|
border: 2px solid #A32020;
|
|
|
|
|
}
|
|
|
|
|
""" if blocked else ""
|
|
|
|
|
return f"""
|
|
|
|
|
QCheckBox {{
|
|
|
|
|
color: #DDD;
|
|
|
|
|
}}
|
|
|
|
|
QCheckBox::indicator {{
|
|
|
|
|
width: 16px;
|
|
|
|
|
height: 16px;
|
|
|
|
|
border: 2px solid #888888;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
background: transparent;
|
|
|
|
|
}}
|
|
|
|
|
QCheckBox::indicator:checked {{
|
|
|
|
|
background-color: #7FFFD4;
|
|
|
|
|
border: 2px solid #888888;
|
|
|
|
|
}}
|
|
|
|
|
{blocked_style}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def refresh_socket_checkbox(self, socket_id, selected_ids, current_group_id=None):
|
|
|
|
|
"""根據 selected_ids 推導並更新 socket 的勾選狀態"""
|
|
|
|
|
drone_ids = [did for did in self.drones.keys() if self.get_socket_id(did) == socket_id]
|
|
|
|
|
if not drone_ids:
|
|
|
|
|
@ -684,22 +717,29 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
if not checkbox:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
checked_count = sum(1 for did in drone_ids if did in selected_ids)
|
|
|
|
|
effective_drone_ids = [
|
|
|
|
|
did for did in drone_ids
|
|
|
|
|
if current_group_id is None
|
|
|
|
|
or did in selected_ids
|
|
|
|
|
or not self._is_drone_selected_by_other_group(did, current_group_id)
|
|
|
|
|
]
|
|
|
|
|
checked_count = sum(1 for did in effective_drone_ids if did in selected_ids)
|
|
|
|
|
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
|
if checked_count == 0:
|
|
|
|
|
checkbox.setCheckState(Qt.CheckState.Unchecked)
|
|
|
|
|
elif checked_count == len(drone_ids):
|
|
|
|
|
elif checked_count == len(effective_drone_ids):
|
|
|
|
|
checkbox.setCheckState(Qt.CheckState.Checked)
|
|
|
|
|
else:
|
|
|
|
|
checkbox.setCheckState(Qt.CheckState.PartiallyChecked)
|
|
|
|
|
checkbox.setEnabled(len(effective_drone_ids) > 0)
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 【已遷移至 refresh_socket_checkbox()】舊的 update_group_checkbox_state() 已廢棄
|
|
|
|
|
|
|
|
|
|
def _handle_assign_drones(self, group_id):
|
|
|
|
|
"""開啟無人機分配對話框 — 直接修改 selected_drone_ids"""
|
|
|
|
|
"""開啟無人機分配對話框 — 過濾被占用的drone"""
|
|
|
|
|
group = self.mission_groups.get(group_id)
|
|
|
|
|
if not group:
|
|
|
|
|
return
|
|
|
|
|
@ -708,10 +748,20 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
# 預先選中:目前 group 的 selected_drone_ids
|
|
|
|
|
pre_selected = set(group.selected_drone_ids)
|
|
|
|
|
|
|
|
|
|
# 允許多 group 分配,所以不需要 other_assigned 過濾
|
|
|
|
|
dialog = DroneAssignDialog(self, all_ids, pre_selected, {})
|
|
|
|
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
|
|
|
group.selected_drone_ids = dialog.get_selected()
|
|
|
|
|
requested = dialog.get_selected()
|
|
|
|
|
|
|
|
|
|
allowed = set()
|
|
|
|
|
blocked = set()
|
|
|
|
|
|
|
|
|
|
for did in requested:
|
|
|
|
|
if self._is_drone_selected_by_other_group(did, group_id):
|
|
|
|
|
blocked.add(did)
|
|
|
|
|
else:
|
|
|
|
|
allowed.add(did)
|
|
|
|
|
|
|
|
|
|
group.selected_drone_ids = allowed
|
|
|
|
|
|
|
|
|
|
# 只有當操作目標組是 active 組時,才更新 UI
|
|
|
|
|
if group_id == self.active_group_id:
|
|
|
|
|
@ -722,8 +772,14 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
panel.update_drone_list()
|
|
|
|
|
panel.update_status()
|
|
|
|
|
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
|
f"Group {group_id}: 已分配 {len(group.selected_drone_ids)} 台無人機", 3000)
|
|
|
|
|
if blocked:
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
|
f"⚠ Group {group_id}: {len(blocked)} 台已被其他 group 勾選,未加入", 4000
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
|
f"Group {group_id}: 已分配 {len(group.selected_drone_ids)} 台無人機", 3000
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _handle_mission_type_changed(self, group_id, mission_type):
|
|
|
|
|
"""群組任務類型切換"""
|
|
|
|
|
@ -896,18 +952,27 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
f"請在地圖上框選要分配到 Group {group_id} 的無人機", 5000)
|
|
|
|
|
|
|
|
|
|
def _handle_drone_box_selected(self, drone_ids_json):
|
|
|
|
|
"""地圖框選完成 — 直接分配到指定群組的 selected_drone_ids"""
|
|
|
|
|
group_id = self._pending_box_assign
|
|
|
|
|
self._pending_box_assign = None
|
|
|
|
|
if not group_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
group = self.mission_groups.get(group_id)
|
|
|
|
|
if not group:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
drone_ids = json.loads(drone_ids_json)
|
|
|
|
|
group.selected_drone_ids = set(drone_ids)
|
|
|
|
|
|
|
|
|
|
# 只有當操作目標組是 active 組時,才更新 UI
|
|
|
|
|
allowed = set()
|
|
|
|
|
blocked = set()
|
|
|
|
|
for did in drone_ids:
|
|
|
|
|
if self._is_drone_selected_by_other_group(did, group_id):
|
|
|
|
|
blocked.add(did)
|
|
|
|
|
else:
|
|
|
|
|
allowed.add(did)
|
|
|
|
|
|
|
|
|
|
group.selected_drone_ids = allowed
|
|
|
|
|
|
|
|
|
|
if group_id == self.active_group_id:
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
|
|
|
|
|
@ -916,8 +981,14 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
panel.update_drone_list()
|
|
|
|
|
panel.update_status()
|
|
|
|
|
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
|
f"Group {group_id}: 框選分配 {len(drone_ids)} 台無人機", 3000)
|
|
|
|
|
if blocked:
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
|
f"⚠ Group {group_id}: 部分 drone 已被其他 group 勾選,僅加入 {len(allowed)} 台", 4000
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
|
f"Group {group_id}: 框選分配 {len(allowed)} 台無人機", 3000
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _handle_select_all_for_group(self, group_id):
|
|
|
|
|
"""全選/取消全選 — Toggle 邏輯"""
|
|
|
|
|
@ -925,17 +996,17 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
if not group:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
all_ids = set(self.drones.keys())
|
|
|
|
|
selectable_ids = self._get_selectable_drones_for_group(group_id) | set(group.selected_drone_ids)
|
|
|
|
|
|
|
|
|
|
# Toggle 邏輯:如果已全選,則清空;否則全選
|
|
|
|
|
if group.selected_drone_ids == all_ids:
|
|
|
|
|
if group.selected_drone_ids == selectable_ids:
|
|
|
|
|
# 已全選 → 清空
|
|
|
|
|
group.selected_drone_ids.clear()
|
|
|
|
|
msg_status = "已取消全選"
|
|
|
|
|
else:
|
|
|
|
|
# 未全選 → 全選
|
|
|
|
|
group.selected_drone_ids = set(all_ids)
|
|
|
|
|
msg_status = f"全選分配 {len(all_ids)} 台無人機"
|
|
|
|
|
group.selected_drone_ids = set(selectable_ids)
|
|
|
|
|
msg_status = f"全選可用無人機 {len(selectable_ids)} 台"
|
|
|
|
|
|
|
|
|
|
# 只有當操作目標組是 active 組時,才更新 UI
|
|
|
|
|
if group_id == self.active_group_id:
|
|
|
|
|
@ -1070,15 +1141,71 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
drone_ids = [did for did in self.drones.keys() if self.get_socket_id(did) == socket_id]
|
|
|
|
|
is_checked = (state == Qt.CheckState.Checked.value)
|
|
|
|
|
selectable = []
|
|
|
|
|
blocked = []
|
|
|
|
|
for did in drone_ids:
|
|
|
|
|
if self._is_drone_selected_by_other_group(did, group.group_id):
|
|
|
|
|
blocked.append(did)
|
|
|
|
|
else:
|
|
|
|
|
selectable.append(did)
|
|
|
|
|
|
|
|
|
|
if is_checked:
|
|
|
|
|
group.selected_drone_ids.update(drone_ids)
|
|
|
|
|
if not selectable:
|
|
|
|
|
if blocked:
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
|
f"⚠ Socket {socket_id} 的 drone 已被其他 group 勾選,無法操作", 4000
|
|
|
|
|
)
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
all_selectable_selected = all(did in group.selected_drone_ids for did in selectable)
|
|
|
|
|
|
|
|
|
|
if all_selectable_selected:
|
|
|
|
|
group.selected_drone_ids.difference_update(selectable)
|
|
|
|
|
else:
|
|
|
|
|
group.selected_drone_ids.difference_update(drone_ids)
|
|
|
|
|
group.selected_drone_ids.update(selectable)
|
|
|
|
|
if blocked:
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
|
f"⚠ 以下 drone 已被其他 group 勾選,略過: {', '.join(blocked)}", 4000
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
|
|
|
|
|
def _is_drone_selected_by_other_group(self, drone_id, current_group_id):
|
|
|
|
|
"""
|
|
|
|
|
檢查該 drone 是否已被其他 group 勾選
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
drone_id: 無人機 ID
|
|
|
|
|
current_group_id: 當前 group 的 ID
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
True 如果 drone 已被其他 group 選擇,否則 False
|
|
|
|
|
"""
|
|
|
|
|
for gid, group in self.mission_groups.items():
|
|
|
|
|
# 跳過當前 group
|
|
|
|
|
if gid == current_group_id:
|
|
|
|
|
continue
|
|
|
|
|
# 檢查該 drone 是否在其他 group 中被勾選
|
|
|
|
|
if drone_id in group.selected_drone_ids:
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _get_selectable_drones_for_group(self, group_id):
|
|
|
|
|
"""
|
|
|
|
|
取得指定 group 可以選擇的 drone 清單(不被其他 group 佔用的)
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
group_id: group 的 ID
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
set: 可選擇的 drone ID 集合
|
|
|
|
|
"""
|
|
|
|
|
selectable = set()
|
|
|
|
|
for did in self.drones.keys():
|
|
|
|
|
if not self._is_drone_selected_by_other_group(did, group_id):
|
|
|
|
|
selectable.add(did)
|
|
|
|
|
return selectable
|
|
|
|
|
|
|
|
|
|
def handle_drone_selection(self, drone_id, state):
|
|
|
|
|
"""單台 drone 勾選 — 只修改 active group 的 selected_drone_ids"""
|
|
|
|
|
group = self._get_active_group()
|
|
|
|
|
@ -1088,6 +1215,12 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
is_checked = (state == Qt.CheckState.Checked.value)
|
|
|
|
|
|
|
|
|
|
if is_checked:
|
|
|
|
|
if self._is_drone_selected_by_other_group(drone_id, group.group_id):
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
|
f"⚠ {drone_id} 已被其他 group 勾選,無法加入 Group {group.group_id}", 3000
|
|
|
|
|
)
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
return
|
|
|
|
|
group.selected_drone_ids.add(drone_id)
|
|
|
|
|
else:
|
|
|
|
|
group.selected_drone_ids.discard(drone_id)
|
|
|
|
|
@ -1103,6 +1236,13 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
if drone_id in group.selected_drone_ids:
|
|
|
|
|
group.selected_drone_ids.remove(drone_id)
|
|
|
|
|
else:
|
|
|
|
|
# 嘗試勾選前先檢查是否被其他 group 使用
|
|
|
|
|
if self._is_drone_selected_by_other_group(drone_id, group.group_id):
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
|
f"⚠ {drone_id} 已被其他 group 勾選,無法加入 Group {group.group_id}", 3000
|
|
|
|
|
)
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
return
|
|
|
|
|
group.selected_drone_ids.add(drone_id)
|
|
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
@ -1113,14 +1253,14 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
if not group:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
all_ids = set(self.drones.keys())
|
|
|
|
|
selectable_ids = self._get_selectable_drones_for_group(group.group_id) | set(group.selected_drone_ids)
|
|
|
|
|
|
|
|
|
|
if group.selected_drone_ids == all_ids:
|
|
|
|
|
if group.selected_drone_ids == selectable_ids:
|
|
|
|
|
# 已全選 → 清空
|
|
|
|
|
group.selected_drone_ids.clear()
|
|
|
|
|
else:
|
|
|
|
|
# 未全選 → 全選
|
|
|
|
|
group.selected_drone_ids = set(all_ids)
|
|
|
|
|
group.selected_drone_ids = set(selectable_ids)
|
|
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
|
|
|
|
|
|