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