sistemati anche i tooltip dei messaggi B

This commit is contained in:
VALLONGOL 2026-01-12 12:38:47 +01:00
parent ac843839bb
commit 7d15cf143a
5 changed files with 282 additions and 8 deletions

View File

@ -1,5 +1,5 @@
{
"window_geometry": "1600x1099+52+52",
"window_geometry": "1600x1099+298+121",
"main_sash_position": [
1,
793

View File

@ -481,6 +481,11 @@ class MonitorApp(tk.Frame):
row_fr.pack(fill=tk.X, padx=2, pady=1)
lbl = tk.Label(row_fr, text=name, width=28, anchor=tk.W)
lbl.pack(side=tk.LEFT)
# attach tooltip to the label itself (description will be added later for widgets)
try:
from .monitor_helpers import UNSET_TEXT
except Exception:
UNSET_TEXT = '<unset>'
# determine per-field editability: make mission date/time readonly
root_field = full.split('.')[0]
readonly_roots = ('date_of_mission', 'time_of_mission')
@ -524,6 +529,21 @@ class MonitorApp(tk.Frame):
if editable:
cb.bind('<<ComboboxSelected>>', lambda e, path=full: self._on_field_changed(path))
cb.pack(side=tk.RIGHT, fill=tk.X, expand=True)
# attach tooltip showing message-field and unit when available
try:
msg_label = getattr(self, 'current_form_label', '')
unit = None
try:
unit = self._find_unit_for_field(msg_label, name)
except Exception:
unit = None
tt = f"{msg_label} - {name}"
if unit:
tt = f"{tt} ({unit})"
from pymsc.gui.components.base_widgets import ToolTip
ToolTip(cb, tt)
except Exception:
pass
widget = ('combobox', cb, enum_items)
else:
# numeric or text entry
@ -550,6 +570,21 @@ class MonitorApp(tk.Frame):
ent.bind('<Return>', lambda e, path=full: self._on_entry_finished(path))
ent.bind('<FocusOut>', lambda e, path=full: self._on_entry_finished(path))
ent.pack(side=tk.RIGHT, fill=tk.X, expand=True)
# attach tooltip showing message-field and unit when available
try:
msg_label = getattr(self, 'current_form_label', '')
unit = None
try:
unit = self._find_unit_for_field(msg_label, name)
except Exception:
unit = None
tt = f"{msg_label} - {name}"
if unit:
tt = f"{tt} ({unit})"
from pymsc.gui.components.base_widgets import ToolTip
ToolTip(ent, tt)
except Exception:
pass
widget = ('entry', ent)
# register created widget for later updates; values will be filled
@ -559,6 +594,69 @@ class MonitorApp(tk.Frame):
except Exception:
pass
def _find_unit_for_field(self, message_label: str, field_name: str):
"""Find unit string for given message label and field by inspecting command registry."""
try:
import pymsc.gui.command_registry as reg
from pymsc.utils import converters as conv
except Exception:
return None
lists = [
getattr(reg, 'CHECKBOXES', []),
getattr(reg, 'COMBOBOXES', []),
getattr(reg, 'SPINBOXES', []),
getattr(reg, 'LIST_CONTROLS', []),
getattr(reg, 'LABELS', [])
]
for lst in lists:
for item in lst:
# check up to 3 message/field pairs
for i in (1, 2, 3):
mkey = f"message{i}" if f"message{i}" in item else ('message' if 'message' in item and i == 1 else None)
fkey = f"field{i}" if f"field{i}" in item else ('field' if 'field' in item and i == 1 else None)
if not mkey or not fkey:
continue
msgobj = item.get(mkey)
fieldreg = item.get(fkey)
if not msgobj or not fieldreg:
continue
try:
mid = getattr(msgobj, 'message_id', None)
except Exception:
mid = None
if mid != message_label:
continue
# compare field names by suffix
try:
if isinstance(fieldreg, str) and (fieldreg.lower().endswith(field_name.lower()) or fieldreg.lower().endswith('_' + field_name.lower())):
# found matching registry item - determine unit based on lsb/scale
# look for lsb/scale keys
lsb = item.get(f"lsb{i}", item.get('lsb'))
scale = item.get(f"scale{i}", item.get('scale', 1))
# map known scales to units
if scale == getattr(conv, 'RAD_TO_DEG', None):
return 'deg'
if scale == getattr(conv, 'MS_TO_KNOTS', None):
return 'kts'
if scale == getattr(conv, 'METERS_TO_FEET', None):
return 'ft'
# check lsb-based heuristics
if lsb == getattr(conv, 'SEMICIRCLE_RAD_LSB', None) or lsb == getattr(conv, 'SEMICIRCLE_LSB', None):
return 'deg' if scale == getattr(conv, 'RAD_TO_DEG', None) else 'rad'
if lsb == getattr(conv, 'GEOPOS_RAD_LSB', None):
return 'deg'
if lsb == getattr(conv, 'CRS_SLAVE_RNG_METERS_LSB', None) or 'range' in field_name.lower() or 'lat' in field_name.lower() or 'lon' in field_name.lower():
# assume distance - if scale converts to feet, show ft otherwise meters
if scale == getattr(conv, 'METERS_TO_FEET', None):
return 'ft'
return 'm'
return None
except Exception:
continue
return None
def _on_entry_focus_in(self, field_path):
"""Mark entry as being edited to prevent refresh from overwriting user input."""
try:

View File

@ -432,7 +432,7 @@ LIST_CONTROLS = [
"message2": msg_a4, "field2": "a4_radio_altimeter_invalid", "type2": "checkbox", "label2": "inv", "tooltip2": "inv"
},
{
"command": "attitude", "label": "Invalid", "description": "Set attitude invalid",
"command": "attitude", "label": "Attitude invalid", "description": "Set attitude invalid",
"message1": msg_a4, "field1": "ATTITUDE_INVALID", "type1": "checkbox", "tooltip1": "A4 ATT INV"
},
{

View File

@ -11,6 +11,44 @@ from pymsc.utils.converters import (
FEET_TO_METERS, MS_TO_KNOTS, KNOTS_TO_MS
)
def _infer_unit_from_info(info: Dict, idx: int):
"""Infer a short unit string from a command registry `info` dict.
Looks for `lsb{idx}` / `scale{idx}` or fallback to `lsb`/`scale`.
Returns unit like 'deg', 'rad', 'kts', 'm', 'ft' or None.
"""
try:
suffix = '' if idx == -1 or idx == 1 else str(idx)
lsb = info.get(f'lsb{suffix}', info.get('lsb'))
scale = info.get(f'scale{suffix}', info.get('scale'))
# Angle in degrees if scale==RAD_TO_DEG
if scale == RAD_TO_DEG:
return 'deg'
# Speed in knots
if scale == MS_TO_KNOTS:
return 'kts'
# Feet/meter mapping
if scale == METERS_TO_FEET:
return 'ft'
# heuristics based on lsb
from pymsc.utils import converters as conv
if lsb in (getattr(conv, 'SEMICIRCLE_RAD_LSB', None), getattr(conv, 'SEMICIRCLE_LSB', None)):
# if a scale converts to degrees, label deg, else rad
if scale == RAD_TO_DEG:
return 'deg'
return 'rad'
if lsb == getattr(conv, 'GEOPOS_RAD_LSB', None):
return 'deg'
if lsb in (getattr(conv, 'CRS_SLAVE_RNG_METERS_LSB', None), getattr(conv, 'VELOCITY_METERS_LSB', None)):
# distance or velocity in meters -> show 'm' or 'ft' depending on scale
if scale == METERS_TO_FEET:
return 'ft'
return 'm'
except Exception:
return None
return None
# Registry for finding widgets by their label (used for automation/scripts)
WIDGET_MAP: Dict[str, Any] = {}
@ -61,6 +99,20 @@ class CommandFrameCheckBox(BaseCommandFrame):
width=self.column_widths[1]
)
self.command_check.grid(row=0, column=1)
# attach tooltip on the interactive control: "<message_id> - <field>"
try:
msg_obj = self.info.get('message')
if msg_obj and hasattr(msg_obj, 'message_id'):
tt = f"{msg_obj.message_id} - {self.info.get('field','')}"
# append short unit to tooltip for editable control
unit = _infer_unit_from_info(self.info, 1)
if unit:
tt = f"{tt} ({unit})"
else:
tt = self.info.get('tooltip', self.info.get('description',''))
ToolTip(self.command_check, tt)
except Exception:
pass
initial_val = self.info['message'].get_value_for_field(self.info['field'])
self.command_var.set(bool(initial_val))
@ -71,6 +123,20 @@ class CommandFrameCheckBox(BaseCommandFrame):
width=self.column_widths[2]
)
self.tellback_check.grid(row=0, column=2)
# attach tooltip to tellback showing its source message-field (B-message)
try:
msg_tb = self.info.get('message_tb')
field_tb = self.info.get('field_tb')
if msg_tb and hasattr(msg_tb, 'message_id') and field_tb:
tt_tb = f"{msg_tb.message_id} - {field_tb}"
unit_tb = _infer_unit_from_info(self.info, -1)
if unit_tb:
tt_tb = f"{tt_tb} ({unit_tb})"
else:
tt_tb = self.info.get('tooltip_tb', self.info.get('description',''))
ToolTip(self.tellback_check, tt_tb)
except Exception:
pass
def on_toggle(self):
# Skip if message is None (disabled field)
@ -137,6 +203,16 @@ class CommandFrameComboBox(BaseCommandFrame):
)
self.combo.grid(row=0, column=1)
self.combo.bind("<<ComboboxSelected>>", self.on_select)
# attach tooltip to combobox showing message-field when possible
try:
msg_obj = self.info.get('message')
if msg_obj and hasattr(msg_obj, 'message_id'):
tt = f"{msg_obj.message_id} - {self.info.get('field','')}"
else:
tt = self.info.get('tooltip', self.info.get('description',''))
ToolTip(self.combo, tt)
except Exception:
pass
if self.info.get('message_tb'):
self.tb_var = tk.StringVar(value="---")
@ -145,6 +221,20 @@ class CommandFrameComboBox(BaseCommandFrame):
width=self.column_widths[2], relief="sunken"
)
self.tb_label.grid(row=0, column=2)
# tooltip for tellback cell: show B-message.field mapping
try:
msg_tb = self.info.get('message_tb')
field_tb = self.info.get('field_tb')
if msg_tb and hasattr(msg_tb, 'message_id') and field_tb:
tt_tb = f"{msg_tb.message_id} - {field_tb}"
unit_tb = _infer_unit_from_info(self.info, -1)
if unit_tb:
tt_tb = f"{tt_tb} ({unit_tb})"
else:
tt_tb = self.info.get('tooltip_tb', self.info.get('description',''))
ToolTip(self.tb_label, tt_tb)
except Exception:
pass
def on_select(self, event=None):
self.start_time = time.time()
@ -171,6 +261,11 @@ class CommandFrameComboBox(BaseCommandFrame):
val_tb_text = self.info["values_tb"][idx_tb]
except (IndexError, KeyError, TypeError):
val_tb_text = "ERR"
# append unit if available (combobox tellbacks are typically enums, but keep flexible)
unit = _infer_unit_from_info(self.info, -1)
if unit:
self.tb_var.set(f"{val_tb_text} ({unit})")
else:
self.tb_var.set(val_tb_text)
elapsed = (time.time() - self.start_time) * 1000
if idx_tb == self.combo.current():
@ -242,6 +337,17 @@ class CommandFrameSpinBox(BaseCommandFrame):
# Also use variable trace to detect arrow changes
self.cmd_var.trace_add("write", self._on_var_change)
# attach tooltip to spinbox showing message-field when possible
try:
msg_obj = self.info.get('message')
if msg_obj and hasattr(msg_obj, 'message_id'):
tt = f"{msg_obj.message_id} - {self.info.get('field','')}"
else:
tt = self.info.get('tooltip', self.info.get('description',''))
ToolTip(self.spin, tt)
except Exception:
pass
if self.info.get('message_tb'):
self.tb_var = tk.StringVar(value="0")
self.tb_label = tk.Label(
@ -249,6 +355,20 @@ class CommandFrameSpinBox(BaseCommandFrame):
width=self.column_widths[2], relief="sunken"
)
self.tb_label.grid(row=0, column=2)
# tooltip for spinbox tellback cell
try:
msg_tb = self.info.get('message_tb')
field_tb = self.info.get('field_tb')
if msg_tb and hasattr(msg_tb, 'message_id') and field_tb:
tt_tb = f"{msg_tb.message_id} - {field_tb}"
unit_tb = _infer_unit_from_info(self.info, -1)
if unit_tb:
tt_tb = f"{tt_tb} ({unit_tb})"
else:
tt_tb = self.info.get('tooltip_tb', self.info.get('description',''))
ToolTip(self.tb_label, tt_tb)
except Exception:
pass
def _validate(self, p):
if p == "" or p == "-":
@ -335,7 +455,16 @@ class CommandFrameSpinBox(BaseCommandFrame):
raw_tb = msg_tb.get_value_for_field(self.info['field_tb'])
readable_tb = get_correct_value(self.info, -1, raw_tb)
fmt = "%.0f" if self.info.get("number_format") == "integer" else "%.4f"
self.tb_var.set(fmt % readable_tb)
# append unit indication
unit = _infer_unit_from_info(self.info, -1)
try:
txt = fmt % readable_tb
except Exception:
txt = str(readable_tb)
if unit:
self.tb_var.set(f"{txt} ({unit})")
else:
self.tb_var.set(txt)
elapsed = (time.time() - self.start_time) * 1000
target = float(self.cmd_var.get())
if abs(readable_tb - target) < 0.0001:
@ -412,7 +541,16 @@ class CommandFrameLabels(tk.Frame):
raw = msg.get_value_for_field(field)
val = get_correct_value(self.info, i, raw)
if val is not None:
self.vars[field].set("%.4f" % val)
fmt = "%.0f" if self.info.get(f"number_format{i}") == "integer" else "%.4f"
unit = _infer_unit_from_info(self.info, i)
try:
txt = fmt % val
except Exception:
txt = str(val)
if unit:
self.vars[field].set(f"{txt} ({unit})")
else:
self.vars[field].set(txt)
else:
self.vars[field].set("N/A")
@ -456,6 +594,15 @@ class CommandFrameControls(tk.Frame):
command=lambda f=field, m=msg: self._on_check(f, m)
)
cb.grid(row=0, column=i)
# attach tooltip to checkbox
try:
if msg and hasattr(msg, 'message_id'):
tt = f"{msg.message_id} - {field}"
else:
tt = self.info.get(f"tooltip{i}", self.info.get('description',''))
ToolTip(cb, tt)
except Exception:
pass
WIDGET_MAP[field] = {"widget": cb, "var": self.vars[field]}
elif ctrl_type == "combobox":
@ -467,6 +614,18 @@ class CommandFrameControls(tk.Frame):
)
cmb.grid(row=0, column=i)
cmb.bind("<<ComboboxSelected>>", lambda e, f=field, m=msg, idx=i: self._on_combo(f, m, idx))
# attach tooltip to combobox
try:
if msg and hasattr(msg, 'message_id'):
tt = f"{msg.message_id} - {field}"
unit = _infer_unit_from_info(self.info, i)
if unit:
tt = f"{tt} ({unit})"
else:
tt = self.info.get(f"tooltip{i}", self.info.get('description',''))
ToolTip(cmb, tt)
except Exception:
pass
WIDGET_MAP[field] = {"widget": cmb, "var": self.vars[field]}
elif ctrl_type == "spinbox":
@ -492,6 +651,18 @@ class CommandFrameControls(tk.Frame):
# Detect arrow button clicks
sp.bind("<ButtonRelease-1>", lambda e, f=field, m=msg, idx=i: self._on_spin_arrow(f, m, idx))
WIDGET_MAP[field] = {"widget": sp, "var": self.vars[field]}
# attach tooltip to spinbox (include unit if available)
try:
if msg and hasattr(msg, 'message_id'):
tt = f"{msg.message_id} - {field}"
else:
tt = self.info.get(f"tooltip{i}", self.info.get('description',''))
unit = _infer_unit_from_info(self.info, i)
if unit:
tt = f"{tt} ({unit})"
ToolTip(sp, tt)
except Exception:
pass
elif ctrl_type == "label":
self.vars[field] = tk.StringVar(value="0")
@ -586,6 +757,11 @@ class CommandFrameControls(tk.Frame):
except IndexError:
self.vars[field].set("ERR")
elif ctrl_type == "spinbox":
# Do not place unit inside editable spinbox value; just format number
fmt = '%.0f' if self.info.get(f"number_format{i}") == "integer" else '%.4f'
try:
self.vars[field].set(fmt % readable)
except Exception:
self.vars[field].set(readable)
elif ctrl_type == "label":
self.vars[field].set("%.4f" % readable)

View File

@ -26,7 +26,7 @@ class MissionPage(ScrollableFrame):
self.f2 = ttk.LabelFrame(self.scrollable_content, text="Attitude (SNU)")
self.f2.grid(row=1, column=0, padx=10, pady=5, sticky="nsew")
atts = [
"attitude", "true_heading", "magnetic_heading",
"attitude", "nav_data_invalid", "true_heading", "magnetic_heading",
"platform_azimuth", "yaw_snu", "pitch_snu", "roll_snu"
]
for a in atts:
@ -50,7 +50,7 @@ class MissionPage(ScrollableFrame):
# 5. Velocities
self.f5 = ttk.LabelFrame(self.scrollable_content, text="Velocities")
self.f5.grid(row=1, column=1, padx=10, pady=5, sticky="nsew")
vels = ["tas", "cas", "nav_data_invalid", "vx/vy/vz", "accx/accy/accz", "nx/ny/nz", "wind"]
vels = ["tas", "cas", "vx/vy/vz", "accx/accy/accz", "nx/ny/nz", "wind"]
for v in vels:
create_widget_by_id(self.f5, v).pack(fill=tk.X, padx=5, pady=2)