206 lines
7.6 KiB
Python
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
|