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 page_index: None,
190 parent: None,
191 children: vec![],
192 object_id: None,
193 has_actions: false,
194 mk: None,
195 border_style: None,
196 });
197 (tree, id)
198 }
199
200 #[test]
201 fn checkbox_initially_off() {
202 let (tree, id) = make_checkbox();
203 assert!(!is_checked(&tree, id));
204 }
205 #[test]
206 fn toggle_checkbox_on_off() {
207 let (mut tree, id) = make_checkbox();
208 assert!(toggle_checkbox(&mut tree, id));
209 assert!(is_checked(&tree, id));
210 assert!(toggle_checkbox(&mut tree, id));
211 assert!(!is_checked(&tree, id));
212 }
213 #[test]
214 fn radio_mutual_exclusion() {
215 let mut tree = FieldTree::new();
216 let group = tree.alloc(FieldNode {
217 partial_name: "rg".into(),
218 alternate_name: None,
219 mapping_name: None,
220 field_type: Some(FieldType::Button),
221 flags: FieldFlags::from_bits((1 << 15) | (1 << 14)),
222 value: Some(FieldValue::Text("Off".into())),
223 default_value: None,
224 default_appearance: None,
225 quadding: None,
226 max_len: None,
227 options: vec![],
228 top_index: None,
229 rect: None,
230 appearance_state: None,
231 page_index: None,
232 parent: None,
233 children: vec![],
234 object_id: None,
235 has_actions: false,
236 mk: None,
237 border_style: None,
238 });
239 let mk = |tree: &mut FieldTree, n: &str| -> FieldId {
240 let id = tree.alloc(FieldNode {
241 partial_name: n.into(),
242 alternate_name: None,
243 mapping_name: None,
244 field_type: None,
245 flags: FieldFlags::from_bits((1 << 15) | (1 << 14)),
246 value: Some(FieldValue::Text("Off".into())),
247 default_value: None,
248 default_appearance: None,
249 quadding: None,
250 max_len: None,
251 options: vec![],
252 top_index: None,
253 rect: Some([0.0, 0.0, 12.0, 12.0]),
254 appearance_state: Some("Off".into()),
255 page_index: None,
256 parent: Some(group),
257 children: vec![],
258 object_id: None,
259 has_actions: false,
260 mk: None,
261 border_style: None,
262 });
263 tree.get_mut(group).children.push(id);
264 id
265 };
266 let r1 = mk(&mut tree, "opt1");
267 let r2 = mk(&mut tree, "opt2");
268 assert!(select_radio(&mut tree, r1));
269 assert!(is_checked(&tree, r1));
270 assert!(!is_checked(&tree, r2));
271 assert!(select_radio(&mut tree, r2));
272 assert!(!is_checked(&tree, r1));
273 assert!(is_checked(&tree, r2));
274 }
275}