Files
elmprodvpn/selective-vpn-gui/main_window/singbox/cards_mixin.py

206 lines
7.6 KiB
Python

from __future__ import annotations
from PySide6.QtCore import QSize, Qt
from PySide6.QtWidgets import QFrame, QLabel, QListWidgetItem, QVBoxLayout
from main_window.constants import SINGBOX_STATUS_ROLE
from transport_protocol_summary import transport_protocol_summary
class SingBoxCardsMixin:
def _singbox_client_protocol_summary(self, client) -> str:
protocol_txt = transport_protocol_summary(client)
if protocol_txt == "n/a":
cid = str(getattr(client, "id", "") or "").strip()
if (
cid
and cid == str(self._singbox_editor_profile_client_id or "").strip()
and str(self._singbox_editor_protocol or "").strip()
):
protocol_txt = str(self._singbox_editor_protocol).strip().lower()
return protocol_txt
def _make_singbox_profile_card_widget(
self,
*,
name: str,
protocol_txt: str,
status: str,
latency_txt: str,
cid: str,
) -> QFrame:
frame = QFrame()
frame.setObjectName("singboxProfileCard")
lay = QVBoxLayout(frame)
lay.setContentsMargins(10, 8, 10, 8)
lay.setSpacing(2)
lbl_name = QLabel(name)
lbl_name.setObjectName("cardName")
lbl_name.setAlignment(Qt.AlignHCenter)
lay.addWidget(lbl_name)
lbl_proto = QLabel(protocol_txt)
lbl_proto.setObjectName("cardProto")
lbl_proto.setAlignment(Qt.AlignHCenter)
lay.addWidget(lbl_proto)
lbl_state = QLabel(f"{str(status or '').upper()} · {latency_txt}")
lbl_state.setObjectName("cardState")
lbl_state.setAlignment(Qt.AlignHCenter)
lay.addWidget(lbl_state)
frame.setToolTip(f"{cid}\n{protocol_txt}\nstatus={status}")
return frame
def _style_singbox_profile_card_widget(
self,
card: QFrame,
*,
active: bool,
selected: bool,
) -> None:
if active and selected:
bg = "#c7f1d5"
border = "#208f47"
name_color = "#11552e"
meta_color = "#1f6f43"
elif active:
bg = "#eafaf0"
border = "#2f9e44"
name_color = "#14532d"
meta_color = "#1f6f43"
elif selected:
bg = "#e8f1ff"
border = "#2f80ed"
name_color = "#1b2f50"
meta_color = "#28568a"
else:
bg = "#f7f7f7"
border = "#c9c9c9"
name_color = "#202020"
meta_color = "#666666"
card.setStyleSheet(
f"""
QFrame#singboxProfileCard {{
border: 1px solid {border};
border-radius: 6px;
background: {bg};
}}
QLabel#cardName {{
color: {name_color};
font-weight: 600;
}}
QLabel#cardProto {{
color: {meta_color};
}}
QLabel#cardState {{
color: {meta_color};
}}
"""
)
def _refresh_singbox_profile_card_styles(self) -> None:
current_id = self._selected_transport_engine_id()
for i in range(self.lst_singbox_profile_cards.count()):
item = self.lst_singbox_profile_cards.item(i)
cid = str(item.data(Qt.UserRole) or "").strip()
status = str(item.data(SINGBOX_STATUS_ROLE) or "").strip().lower()
card = self.lst_singbox_profile_cards.itemWidget(item)
if not isinstance(card, QFrame):
continue
self._style_singbox_profile_card_widget(
card,
active=(status == "up"),
selected=bool(current_id and cid == current_id),
)
def _render_singbox_profile_cards(self) -> None:
current_id = self._selected_transport_engine_id()
self.lst_singbox_profile_cards.blockSignals(True)
self.lst_singbox_profile_cards.clear()
selected_item = None
if not self._transport_api_supported:
item = QListWidgetItem("Transport API unavailable")
item.setFlags(item.flags() & ~Qt.ItemIsEnabled & ~Qt.ItemIsSelectable)
self.lst_singbox_profile_cards.addItem(item)
self.lst_singbox_profile_cards.blockSignals(False)
return
if not self._transport_clients:
item = QListWidgetItem("No SingBox profiles configured")
item.setFlags(item.flags() & ~Qt.ItemIsEnabled & ~Qt.ItemIsSelectable)
self.lst_singbox_profile_cards.addItem(item)
self.lst_singbox_profile_cards.blockSignals(False)
return
for c in self._transport_clients:
cid = str(getattr(c, "id", "") or "").strip()
if not cid:
continue
name = str(getattr(c, "name", "") or "").strip() or cid
status, latency, _last_error, _last_check = self._transport_live_health_for_client(c)
latency_txt = f"{latency}ms" if latency > 0 else "no ping"
protocol_txt = self._singbox_client_protocol_summary(c)
item = QListWidgetItem("")
item.setData(Qt.UserRole, cid)
item.setData(SINGBOX_STATUS_ROLE, status)
item.setSizeHint(QSize(228, 78))
self.lst_singbox_profile_cards.addItem(item)
self.lst_singbox_profile_cards.setItemWidget(
item,
self._make_singbox_profile_card_widget(
name=name,
protocol_txt=protocol_txt,
status=status,
latency_txt=latency_txt,
cid=cid,
),
)
if current_id and cid == current_id:
selected_item = item
if selected_item is not None:
self.lst_singbox_profile_cards.setCurrentItem(selected_item)
elif self.lst_singbox_profile_cards.count() > 0:
self.lst_singbox_profile_cards.setCurrentRow(0)
self.lst_singbox_profile_cards.blockSignals(False)
self._refresh_singbox_profile_card_styles()
def _sync_singbox_profile_card_selection(self, cid: str) -> None:
if self._syncing_singbox_selection:
return
self._syncing_singbox_selection = True
try:
self.lst_singbox_profile_cards.blockSignals(True)
self.lst_singbox_profile_cards.clearSelection()
target = str(cid or "").strip()
if target:
for i in range(self.lst_singbox_profile_cards.count()):
item = self.lst_singbox_profile_cards.item(i)
if str(item.data(Qt.UserRole) or "").strip() == target:
self.lst_singbox_profile_cards.setCurrentItem(item)
break
self.lst_singbox_profile_cards.blockSignals(False)
finally:
self._syncing_singbox_selection = False
self._refresh_singbox_profile_card_styles()
def _select_transport_engine_by_id(self, cid: str) -> bool:
target = str(cid or "").strip()
if not target:
return False
idx = self.cmb_transport_engine.findData(target)
if idx < 0:
return False
if idx != self.cmb_transport_engine.currentIndex():
self.cmb_transport_engine.setCurrentIndex(idx)
else:
self._sync_singbox_profile_card_selection(target)
self._sync_selected_singbox_profile_link(silent=True)
self._load_singbox_editor_for_selected(silent=True)
self._update_transport_engine_view()
return True