From be52c4bd22e222d2f349bc334ab9b31b2daafba6 Mon Sep 17 00:00:00 2001 From: ken910606 Date: Fri, 24 Apr 2026 18:27:16 +0800 Subject: [PATCH] Update GUI 2.0.8 group selected list --- src/GUI/gui.py | 200 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 170 insertions(+), 30 deletions(-) diff --git a/src/GUI/gui.py b/src/GUI/gui.py index 45b82ec..a332ed3 100644 --- a/src/GUI/gui.py +++ b/src/GUI/gui.py @@ -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,46 +952,61 @@ 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() - + panel = self.group_panels.get(group_id) if panel: 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 邏輯""" group = self.mission_groups.get(group_id) 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() @@ -1787,4 +1927,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main()