use pv::{Pv, PvManager, PvState, Value};
use crate::screen::Node;
pub struct Link {
spec: bob::GroupLink,
pv: Pv,
device: Option<String>,
}
impl Link {
pub fn new(w: &bob::Widget, expand: &dyn Fn(&str) -> String, pvs: &PvManager) -> Option<Self> {
let spec = w.group_link()?;
let name = expand(&spec.pv_name);
if name.trim().is_empty() {
return None;
}
Some(Link {
pv: pvs.subscribe(&name),
spec,
device: None,
})
}
}
fn device(s: &PvState) -> Option<String> {
if !s.connected {
return None;
}
match &s.value {
Value::Str(t) => bob::group_device(t),
_ => None,
}
}
pub fn update(node: &mut Node, pvs: &PvManager) {
let Some(link) = &node.link else { return };
let Some(device) = device(&link.pv.state()) else {
return;
};
if link.device.as_deref() == Some(device.as_str()) {
return;
}
log::debug!("{}: {} names {device}", node.w.name(), link.pv.name());
let spec = link.spec.clone();
spec.point_at(&mut node.w.props, &device);
if let Some(orig) = &mut node.orig_props {
spec.point_at(orig, &device);
}
if spec.sets == bob::LinkSets::Pv {
node.pv_name = node.prop("pv_name");
node.pv = Some(pvs.subscribe(&node.pv_name));
}
if let Some(link) = &mut node.link {
link.device = Some(device);
}
}
pub fn frame(node: &Node, s: Option<&PvState>) -> Option<i64> {
let link = node.link.as_ref()?;
let top = link.spec.frames?;
link.device.as_ref()?;
let status = s.filter(|s| s.connected).and_then(|s| s.value.as_i64());
Some(bob::group_frame(status, top))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::screen::Builder;
use bob::{Display, Macros};
#[test]
fn linked_widgets_follow_their_link() {
let d = Display::parse(
r#"<display><macros><space>loc://repoint_test</space></macros>
<widget type="symbol"><name></name><pv_name></pv_name>
<tooltip>$(pv_name)
$(pv_value)</tooltip><sub_image_width>34</sub_image_width>
<actions><action type="open_display"><file>gionp.opi</file><macros><device>$(name)</device></macros></action></actions>
<group_link><pv_name>$(space)("")</pv_name><sets>pv</sets><frames>9</frames></group_link></widget>
<widget type="label"><text>SRXXC-VA-GXXX-YY</text>
<group_link><pv_name>$(space)</pv_name><sets>text</sets></group_link></widget>
</display>"#,
)
.unwrap();
let pvs = PvManager::new();
let mut s = Builder::new(&pvs).screen(d, &Macros::default(), None, 0);
let link = s.nodes[0].link.as_ref().unwrap().pv.clone();
for n in &mut s.nodes {
update(n, &pvs);
}
assert!(s.nodes[0].pv.is_none());
assert_eq!(frame(&s.nodes[0], None), None);
assert_eq!(s.nodes[1].prop("text"), "SRXXC-VA-GXXX-YY");
link.write("loc://G1:STA NPP NMS");
for n in &mut s.nodes {
update(n, &pvs);
}
let sym = &s.nodes[0];
assert_eq!(sym.pv_name, "loc://G1:STA");
assert_eq!(sym.pv.as_ref().unwrap().name().to_string(), "loc://G1:STA");
assert_eq!(sym.expand("$(name)"), "loc://G1");
assert_eq!(s.nodes[1].prop("text"), "loc://G1");
let bob::Action::OpenDisplay { macros, .. } = &sym.w.actions()[0] else {
panic!("not an open display action")
};
assert_eq!(macros.get("device"), Some("loc://G1"));
let sta = sym.pv.as_ref().unwrap();
assert_eq!(frame(sym, sym.state().as_ref()), Some(1));
sta.write("3");
assert_eq!(frame(sym, sym.state().as_ref()), Some(4));
sta.write("9");
assert_eq!(frame(sym, sym.state().as_ref()), Some(0));
link.write("loc://G2:CTL:SP NPP MS");
for n in &mut s.nodes {
update(n, &pvs);
}
let sym = &s.nodes[0];
assert_eq!(sym.pv_name, "loc://G2:STA");
assert_eq!(sym.expand("$(name)"), "loc://G2");
assert_eq!(s.nodes[1].prop("text"), "loc://G2");
link.write("loc://G2:STA NPP NMS");
update(&mut s.nodes[0], &pvs);
assert_eq!(s.nodes[0].pv_name, "loc://G2:STA");
}
}