1use crate::flags::FieldFlags;
10use crate::tree::*;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ButtonKind {
20 Checkbox,
24 Radio,
28 PushButton,
32}
33
34pub fn button_kind(flags: FieldFlags) -> ButtonKind {
36 if flags.push_button() {
37 ButtonKind::PushButton
38 } else if flags.radio() {
39 ButtonKind::Radio
40 } else {
41 ButtonKind::Checkbox
42 }
43}
44
45pub fn is_checked(tree: &FieldTree, id: FieldId) -> bool {
47 if let Some(ref state) = tree.get(id).appearance_state {
48 return state != "Off";
49 }
50 matches!(tree.effective_value(id), Some(FieldValue::Text(s)) if s != "Off")
51}
52
53pub fn on_state_name(tree: &FieldTree, id: FieldId) -> String {
55 if let Some(ref state) = tree.get(id).appearance_state {
56 if state != "Off" {
57 return state.clone();
58 }
59 }
60 if let Some(FieldValue::Text(s)) = tree.effective_value(id) {
61 if s != "Off" {
62 return s.clone();
63 }
64 }
65 "Yes".into()
66}
67
68pub fn toggle_checkbox(tree: &mut FieldTree, id: FieldId) -> bool {
70 let flags = tree.effective_flags(id);
71 if flags.read_only() || button_kind(flags) != ButtonKind::Checkbox {
72 return false;
73 }
74 let new_state = if is_checked(tree, id) {
75 "Off".to_string()
76 } else {
77 on_state_name(tree, id)
78 };
79 tree.get_mut(id).value = Some(FieldValue::Text(new_state.clone()));
80 tree.get_mut(id).appearance_state = Some(new_state);
81 true
82}
83
84pub fn select_radio(tree: &mut FieldTree, id: FieldId) -> bool {
86 if tree.effective_flags(id).read_only() {
87 return false;
88 }
89 let on_name = on_state_name(tree, id);
90 if let Some(pid) = tree.get(id).parent {
91 let siblings: Vec<FieldId> = tree.get(pid).children.clone();
92 for sib in siblings {
93 if sib != id {
94 tree.get_mut(sib).value = Some(FieldValue::Text("Off".into()));
95 tree.get_mut(sib).appearance_state = Some("Off".into());
96 }
97 }
98 tree.get_mut(pid).value = Some(FieldValue::Text(on_name.clone()));
99 }
100 tree.get_mut(id).value = Some(FieldValue::Text(on_name.clone()));
101 tree.get_mut(id).appearance_state = Some(on_name);
102 true
103}
104
105#[derive(Debug, Clone)]
111pub struct SubmitAction {
112 pub url: String,
115 pub flags: u32,
119}
120
121#[derive(Debug, Clone)]
127pub struct ResetAction {
128 pub fields: Vec<String>,
131 pub flags: u32,
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum IconCaptionLayout {
145 CaptionOnly,
147 IconOnly,
149 CaptionBelow,
151 CaptionAbove,
153 CaptionRight,
155 CaptionLeft,
157 CaptionOverlay,
159}
160
161impl From<u32> for IconCaptionLayout {
162 fn from(v: u32) -> Self {
163 match v {
164 1 => Self::IconOnly,
165 2 => Self::CaptionBelow,
166 3 => Self::CaptionAbove,
167 4 => Self::CaptionRight,
168 5 => Self::CaptionLeft,
169 6 => Self::CaptionOverlay,
170 _ => Self::CaptionOnly,
171 }
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 fn make_checkbox() -> (FieldTree, FieldId) {
179 let mut tree = FieldTree::new();
180 let id = tree.alloc(FieldNode {
181 partial_name: "cb".into(),
182 alternate_name: None,
183 mapping_name: None,
184 field_type: Some(FieldType::Button),
185 flags: FieldFlags::empty(),
186 value: Some(FieldValue::Text("Off".into())),
187 default_value: None,
188 default_appearance: None,
189 quadding: None,
190 max_len: None,
191 options: vec![],
192 top_index: None,
193 rect: Some([0.0, 0.0, 12.0, 12.0]),
194 appearance_state: Some("Off".into()),
195 on_state: None,
196 page_index: None,
197 parent: None,
198 children: vec![],
199 object_id: None,
200 has_actions: false,
201 mk: None,
202 border_style: None,
203 });
204 (tree, id)
205 }
206
207 #[test]
208 fn checkbox_initially_off() {
209 let (tree, id) = make_checkbox();
210 assert!(!is_checked(&tree, id));
211 }
212 #[test]
213 fn toggle_checkbox_on_off() {
214 let (mut tree, id) = make_checkbox();
215 assert!(toggle_checkbox(&mut tree, id));
216 assert!(is_checked(&tree, id));
217 assert!(toggle_checkbox(&mut tree, id));
218 assert!(!is_checked(&tree, id));
219 }
220 #[test]
221 fn radio_mutual_exclusion() {
222 let mut tree = FieldTree::new();
223 let group = tree.alloc(FieldNode {
224 partial_name: "rg".into(),
225 alternate_name: None,
226 mapping_name: None,
227 field_type: Some(FieldType::Button),
228 flags: FieldFlags::from_bits((1 << 15) | (1 << 14)),
229 value: Some(FieldValue::Text("Off".into())),
230 default_value: None,
231 default_appearance: None,
232 quadding: None,
233 max_len: None,
234 options: vec![],
235 top_index: None,
236 rect: None,
237 appearance_state: None,
238 on_state: None,
239 page_index: None,
240 parent: None,
241 children: vec![],
242 object_id: None,
243 has_actions: false,
244 mk: None,
245 border_style: None,
246 });
247 let mk = |tree: &mut FieldTree, n: &str| -> FieldId {
248 let id = tree.alloc(FieldNode {
249 partial_name: n.into(),
250 alternate_name: None,
251 mapping_name: None,
252 field_type: None,
253 flags: FieldFlags::from_bits((1 << 15) | (1 << 14)),
254 value: Some(FieldValue::Text("Off".into())),
255 default_value: None,
256 default_appearance: None,
257 quadding: None,
258 max_len: None,
259 options: vec![],
260 top_index: None,
261 rect: Some([0.0, 0.0, 12.0, 12.0]),
262 appearance_state: Some("Off".into()),
263 on_state: None,
264 page_index: None,
265 parent: Some(group),
266 children: vec![],
267 object_id: None,
268 has_actions: false,
269 mk: None,
270 border_style: None,
271 });
272 tree.get_mut(group).children.push(id);
273 id
274 };
275 let r1 = mk(&mut tree, "opt1");
276 let r2 = mk(&mut tree, "opt2");
277 assert!(select_radio(&mut tree, r1));
278 assert!(is_checked(&tree, r1));
279 assert!(!is_checked(&tree, r2));
280 assert!(select_radio(&mut tree, r2));
281 assert!(!is_checked(&tree, r1));
282 assert!(is_checked(&tree, r2));
283 }
284}