1use crate::flags::FieldFlags;
4use crate::tree::*;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ButtonKind {
9 Checkbox,
10 Radio,
11 PushButton,
12}
13
14pub fn button_kind(flags: FieldFlags) -> ButtonKind {
16 if flags.push_button() {
17 ButtonKind::PushButton
18 } else if flags.radio() {
19 ButtonKind::Radio
20 } else {
21 ButtonKind::Checkbox
22 }
23}
24
25pub fn is_checked(tree: &FieldTree, id: FieldId) -> bool {
27 if let Some(ref state) = tree.get(id).appearance_state {
28 return state != "Off";
29 }
30 matches!(tree.effective_value(id), Some(FieldValue::Text(s)) if s != "Off")
31}
32
33pub fn on_state_name(tree: &FieldTree, id: FieldId) -> String {
35 if let Some(ref state) = tree.get(id).appearance_state {
36 if state != "Off" {
37 return state.clone();
38 }
39 }
40 if let Some(FieldValue::Text(s)) = tree.effective_value(id) {
41 if s != "Off" {
42 return s.clone();
43 }
44 }
45 "Yes".into()
46}
47
48pub fn toggle_checkbox(tree: &mut FieldTree, id: FieldId) -> bool {
50 let flags = tree.effective_flags(id);
51 if flags.read_only() || button_kind(flags) != ButtonKind::Checkbox {
52 return false;
53 }
54 let new_state = if is_checked(tree, id) {
55 "Off".to_string()
56 } else {
57 on_state_name(tree, id)
58 };
59 tree.get_mut(id).value = Some(FieldValue::Text(new_state.clone()));
60 tree.get_mut(id).appearance_state = Some(new_state);
61 true
62}
63
64pub fn select_radio(tree: &mut FieldTree, id: FieldId) -> bool {
66 if tree.effective_flags(id).read_only() {
67 return false;
68 }
69 let on_name = on_state_name(tree, id);
70 if let Some(pid) = tree.get(id).parent {
71 let siblings: Vec<FieldId> = tree.get(pid).children.clone();
72 for sib in siblings {
73 if sib != id {
74 tree.get_mut(sib).value = Some(FieldValue::Text("Off".into()));
75 tree.get_mut(sib).appearance_state = Some("Off".into());
76 }
77 }
78 tree.get_mut(pid).value = Some(FieldValue::Text(on_name.clone()));
79 }
80 tree.get_mut(id).value = Some(FieldValue::Text(on_name.clone()));
81 tree.get_mut(id).appearance_state = Some(on_name);
82 true
83}
84
85#[derive(Debug, Clone)]
87pub struct SubmitAction {
88 pub url: String,
89 pub flags: u32,
90}
91
92#[derive(Debug, Clone)]
94pub struct ResetAction {
95 pub fields: Vec<String>,
96 pub flags: u32,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum IconCaptionLayout {
102 CaptionOnly,
103 IconOnly,
104 CaptionBelow,
105 CaptionAbove,
106 CaptionRight,
107 CaptionLeft,
108 CaptionOverlay,
109}
110
111impl From<u32> for IconCaptionLayout {
112 fn from(v: u32) -> Self {
113 match v {
114 1 => Self::IconOnly,
115 2 => Self::CaptionBelow,
116 3 => Self::CaptionAbove,
117 4 => Self::CaptionRight,
118 5 => Self::CaptionLeft,
119 6 => Self::CaptionOverlay,
120 _ => Self::CaptionOnly,
121 }
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 fn make_checkbox() -> (FieldTree, FieldId) {
129 let mut tree = FieldTree::new();
130 let id = tree.alloc(FieldNode {
131 partial_name: "cb".into(),
132 alternate_name: None,
133 mapping_name: None,
134 field_type: Some(FieldType::Button),
135 flags: FieldFlags::empty(),
136 value: Some(FieldValue::Text("Off".into())),
137 default_value: None,
138 default_appearance: None,
139 quadding: None,
140 max_len: None,
141 options: vec![],
142 top_index: None,
143 rect: Some([0.0, 0.0, 12.0, 12.0]),
144 appearance_state: Some("Off".into()),
145 page_index: None,
146 parent: None,
147 children: vec![],
148 object_id: None,
149 has_actions: false,
150 mk: None,
151 border_style: None,
152 });
153 (tree, id)
154 }
155
156 #[test]
157 fn checkbox_initially_off() {
158 let (tree, id) = make_checkbox();
159 assert!(!is_checked(&tree, id));
160 }
161 #[test]
162 fn toggle_checkbox_on_off() {
163 let (mut tree, id) = make_checkbox();
164 assert!(toggle_checkbox(&mut tree, id));
165 assert!(is_checked(&tree, id));
166 assert!(toggle_checkbox(&mut tree, id));
167 assert!(!is_checked(&tree, id));
168 }
169 #[test]
170 fn radio_mutual_exclusion() {
171 let mut tree = FieldTree::new();
172 let group = tree.alloc(FieldNode {
173 partial_name: "rg".into(),
174 alternate_name: None,
175 mapping_name: None,
176 field_type: Some(FieldType::Button),
177 flags: FieldFlags::from_bits((1 << 15) | (1 << 14)),
178 value: Some(FieldValue::Text("Off".into())),
179 default_value: None,
180 default_appearance: None,
181 quadding: None,
182 max_len: None,
183 options: vec![],
184 top_index: None,
185 rect: None,
186 appearance_state: None,
187 page_index: None,
188 parent: None,
189 children: vec![],
190 object_id: None,
191 has_actions: false,
192 mk: None,
193 border_style: None,
194 });
195 let mk = |tree: &mut FieldTree, n: &str| -> FieldId {
196 let id = tree.alloc(FieldNode {
197 partial_name: n.into(),
198 alternate_name: None,
199 mapping_name: None,
200 field_type: None,
201 flags: FieldFlags::from_bits((1 << 15) | (1 << 14)),
202 value: Some(FieldValue::Text("Off".into())),
203 default_value: None,
204 default_appearance: None,
205 quadding: None,
206 max_len: None,
207 options: vec![],
208 top_index: None,
209 rect: Some([0.0, 0.0, 12.0, 12.0]),
210 appearance_state: Some("Off".into()),
211 page_index: None,
212 parent: Some(group),
213 children: vec![],
214 object_id: None,
215 has_actions: false,
216 mk: None,
217 border_style: None,
218 });
219 tree.get_mut(group).children.push(id);
220 id
221 };
222 let r1 = mk(&mut tree, "opt1");
223 let r2 = mk(&mut tree, "opt2");
224 assert!(select_radio(&mut tree, r1));
225 assert!(is_checked(&tree, r1));
226 assert!(!is_checked(&tree, r2));
227 assert!(select_radio(&mut tree, r2));
228 assert!(!is_checked(&tree, r1));
229 assert!(is_checked(&tree, r2));
230 }
231}