from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtWidgets import QMenu, QMessageBox class SingBoxRuntimeCardsMixin: def on_singbox_profile_card_context_menu(self, pos) -> None: item = self.lst_singbox_profile_cards.itemAt(pos) if item is None: return cid = str(item.data(Qt.UserRole) or "").strip() if not cid: return menu = QMenu(self) act_run = menu.addAction("Run") act_edit = menu.addAction("Edit") act_delete = menu.addAction("Delete") chosen = menu.exec(self.lst_singbox_profile_cards.viewport().mapToGlobal(pos)) if chosen is None: return if not self._select_transport_engine_by_id(cid): QMessageBox.warning(self, "SingBox profile", f"Profile '{cid}' is no longer available.") return if chosen == act_run: self.on_transport_engine_action("start") return if chosen == act_edit: self.on_singbox_profile_edit_dialog(cid) return if chosen == act_delete: self.on_transport_engine_delete(cid) return def on_singbox_profile_card_selected(self) -> None: if self._syncing_singbox_selection: return items = self.lst_singbox_profile_cards.selectedItems() if not items: return cid = str(items[0].data(Qt.UserRole) or "").strip() if not cid: return idx = self.cmb_transport_engine.findData(cid) if idx < 0: return if idx != self.cmb_transport_engine.currentIndex(): self._syncing_singbox_selection = True try: self.cmb_transport_engine.setCurrentIndex(idx) finally: self._syncing_singbox_selection = False return self._refresh_singbox_profile_card_styles() self._sync_selected_singbox_profile_link(silent=True) self._load_singbox_editor_for_selected(silent=True) self._update_transport_engine_view() def _singbox_value_label(self, key: str, value: str) -> str: v = str(value or "").strip().lower() if key == "routing": if v == "full": return "Full tunnel" return "Selective" if key == "dns": if v == "singbox_dns": return "SingBox DNS" return "System resolver" if key == "killswitch": if v == "off": return "Disabled" return "Enabled" return v or "—" def _effective_singbox_policy(self) -> tuple[str, str, str]: route = str(self.cmb_singbox_global_routing.currentData() or "selective").strip().lower() dns = str(self.cmb_singbox_global_dns.currentData() or "system_resolver").strip().lower() killswitch = str(self.cmb_singbox_global_killswitch.currentData() or "on").strip().lower() if not self.chk_singbox_profile_use_global_routing.isChecked(): route = str(self.cmb_singbox_profile_routing.currentData() or route).strip().lower() if route == "global": route = str(self.cmb_singbox_global_routing.currentData() or "selective").strip().lower() if not self.chk_singbox_profile_use_global_dns.isChecked(): dns = str(self.cmb_singbox_profile_dns.currentData() or dns).strip().lower() if dns == "global": dns = str(self.cmb_singbox_global_dns.currentData() or "system_resolver").strip().lower() if not self.chk_singbox_profile_use_global_killswitch.isChecked(): killswitch = str(self.cmb_singbox_profile_killswitch.currentData() or killswitch).strip().lower() if killswitch == "global": killswitch = str(self.cmb_singbox_global_killswitch.currentData() or "on").strip().lower() return route, dns, killswitch def _refresh_singbox_profile_effective(self) -> None: route, dns, killswitch = self._effective_singbox_policy() route_txt = self._singbox_value_label("routing", route) dns_txt = self._singbox_value_label("dns", dns) kill_txt = self._singbox_value_label("killswitch", killswitch) self.lbl_singbox_profile_effective.setText( f"Effective: routing={route_txt} | dns={dns_txt} | kill-switch={kill_txt}" ) self.lbl_singbox_profile_effective.setStyleSheet("color: gray;") def _apply_singbox_profile_controls(self) -> None: self.cmb_singbox_profile_routing.setEnabled( not self.chk_singbox_profile_use_global_routing.isChecked() ) self.cmb_singbox_profile_dns.setEnabled( not self.chk_singbox_profile_use_global_dns.isChecked() ) self.cmb_singbox_profile_killswitch.setEnabled( not self.chk_singbox_profile_use_global_killswitch.isChecked() ) self._refresh_singbox_profile_effective() def _apply_singbox_compact_visibility(self) -> None: show_profile = bool(self.btn_singbox_toggle_profile_settings.isChecked()) self.grp_singbox_profile_settings.setVisible(show_profile) self.btn_singbox_toggle_profile_settings.setText( "Hide profile settings" if show_profile else "Profile settings" ) show_global = bool(self.btn_singbox_toggle_global_defaults.isChecked()) self.grp_singbox_global_defaults.setVisible(show_global) self.btn_singbox_toggle_global_defaults.setText( "Hide global defaults" if show_global else "Global defaults" ) show_log = bool(self.btn_singbox_toggle_activity.isChecked()) self.grp_singbox_activity.setVisible(show_log) self.btn_singbox_toggle_activity.setText( "Hide activity log" if show_log else "Activity log" )