sistemati anche i tooltip dei messaggi B
This commit is contained in:
parent
ac843839bb
commit
7d15cf143a
@ -1,5 +1,5 @@
|
||||
{
|
||||
"window_geometry": "1600x1099+52+52",
|
||||
"window_geometry": "1600x1099+298+121",
|
||||
"main_sash_position": [
|
||||
1,
|
||||
793
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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"
|
||||
},
|
||||
{
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user