S1005403_RisCC/target_simulator/gui/connection_panel.py

152 lines
5.5 KiB
Python

import tkinter as tk
from tkinter import ttk
from typing import Callable, Optional, Dict, Any
class ConnectionPanel(ttk.LabelFrame):
"""Reusable connection panel used by MainView.
This encapsulates the small UI that shows connection type/summary and
exposes simple hooks so the outer view can attach handlers for Connect
and Settings.
"""
def __init__(self, parent, initial_config: Optional[Dict[str, Any]] = None):
super().__init__(parent, text="Connection")
self._on_connect: Optional[Callable[[], None]] = None
self._on_open_settings: Optional[Callable[[], None]] = None
self.conn_type_var = tk.StringVar(
value=(initial_config or {}).get("target", {}).get("type", "-")
)
self.conn_info_var = tk.StringVar(
value=self._format_summary((initial_config or {}).get("target", {}))
)
ttk.Label(self, text="Type:").pack(side=tk.LEFT, padx=(6, 2))
ttk.Label(self, textvariable=self.conn_type_var, width=10).pack(side=tk.LEFT)
ttk.Label(self, textvariable=self.conn_info_var).pack(side=tk.LEFT, padx=(8, 4))
# Buttons
self.connect_button = ttk.Button(
self, text="Connect", command=self._handle_connect
)
self.connect_button.pack(side=tk.RIGHT, padx=(4, 6))
self.conn_settings_button = ttk.Button(
self, text="Settings...", command=self._handle_open_settings
)
self.conn_settings_button.pack(side=tk.RIGHT, padx=(4, 0))
def _format_summary(self, cfg: Dict[str, Any]) -> str:
"""Return a compact human-readable summary for the connection config.
Args:
cfg (Dict[str, Any]): The connection configuration for a single
communicator (e.g., sfp/serial/tftp sections).
Returns:
str: A short summary suitable for display in the UI.
"""
try:
t = cfg.get("type")
if not t:
return "-"
if t == "sfp":
sfp = cfg.get("sfp", {})
ip = sfp.get("ip") or sfp.get("host") or "?"
remote = sfp.get("port") or sfp.get("remote_port")
if isinstance(remote, (list, tuple)):
remote_str = ",".join(str(int(p)) for p in remote)
else:
try:
remote_str = str(int(remote)) if remote is not None else "?"
except Exception:
remote_str = str(remote)
local = sfp.get("local_port")
if local is not None:
try:
local_str = str(int(local))
except Exception:
local_str = str(local)
return f"{ip} (remote:{remote_str} local:{local_str})"
return f"{ip} (remote:{remote_str})"
if t == "serial":
s = cfg.get("serial", {})
port = s.get("port") or s.get("device") or "?"
baud = s.get("baudrate") or s.get("baud") or "?"
return f"{port} @{baud}"
if t == "tftp":
tftp = cfg.get("tftp", {})
host = tftp.get("host") or tftp.get("server") or "?"
return f"{host}"
return "-"
except Exception:
return "-"
def set_connect_handler(self, cb: Callable[[], None]):
"""Register a callback to be invoked when the Connect button is pressed.
The callback receives no arguments.
"""
self._on_connect = cb
def set_open_settings_handler(self, cb: Callable[[], None]):
"""Register a callback to open connection settings UI.
The callback receives no arguments and is expected to display the
settings window or dialog when invoked.
"""
self._on_open_settings = cb
def _handle_connect(self):
"""Invoke the registered connect handler (if any).
This method delegates to the user-provided handler and ensures that
handler exceptions propagate so the caller can manage them.
"""
if self._on_connect:
try:
self._on_connect()
except Exception:
# Handler should manage its own errors
raise
def _handle_open_settings(self):
"""Invoke the registered 'open settings' handler (if any)."""
if self._on_open_settings:
try:
self._on_open_settings()
except Exception:
raise
def update_summary(self, conn_config: Dict[str, Any]):
"""Update the displayed connection summary according to configuration.
Args:
conn_config (Dict[str, Any]): Full configuration dictionary where
the 'target' key holds the per-communicator section.
"""
try:
self.conn_type_var.set(
(conn_config or {}).get("target", {}).get("type", "-")
)
self.conn_info_var.set(
self._format_summary((conn_config or {}).get("target", {}))
)
except Exception:
pass
def update_toggle_state(self, is_connected: bool):
"""Change the Connect button label to reflect connection state.
Args:
is_connected (bool): If True, button shows 'Disconnect'. Otherwise
it shows 'Connect'.
"""
try:
self.connect_button.config(text="Disconnect" if is_connected else "Connect")
except Exception:
pass