Skip to main content

pdfluent_forms/
choice.rs

1//! Dropdown and listbox implementation (B.4).
2
3use crate::flags::FieldFlags;
4use crate::tree::*;
5
6/// Sub-kind of a choice (`/Ch`) field, derived from its flags word.
7///
8/// AcroForm models combo boxes and list boxes as the same field type;
9/// the four variants below distinguish the visual presentation and
10/// selection semantics. Use [`choice_kind`] to derive this enum from a
11/// [`FieldFlags`] value.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ChoiceKind {
14    /// Closed-list combo box: pick one of the predefined `/Opt` entries.
15    /// Free-text input not allowed. (`Combo` flag set; `Edit` clear.)
16    ComboBox,
17    /// Combo box that also accepts free-text input not in the option list.
18    /// Useful for "common cities, but type your own" patterns.
19    /// (`Combo` and `Edit` both set.)
20    EditableCombo,
21    /// Single-selection list box; one option visible-and-selected at a
22    /// time, others scroll. (`Combo` clear; `MultiSelect` clear.)
23    ListBox,
24    /// List box allowing multiple options to be selected simultaneously
25    /// (Ctrl/Cmd-click). The field's value is then a name array rather
26    /// than a single name. (`MultiSelect` flag set.)
27    MultiSelectListBox,
28}
29
30/// Determine choice sub-kind from flags.
31pub fn choice_kind(flags: FieldFlags) -> ChoiceKind {
32    if flags.combo() {
33        if flags.edit() {
34            ChoiceKind::EditableCombo
35        } else {
36            ChoiceKind::ComboBox
37        }
38    } else if flags.multi_select() {
39        ChoiceKind::MultiSelectListBox
40    } else {
41        ChoiceKind::ListBox
42    }
43}
44
45/// Get the currently selected value(s).
46pub fn get_selection(tree: &FieldTree, id: FieldId) -> Vec<String> {
47    match tree.effective_value(id) {
48        Some(FieldValue::Text(s)) => vec![s.clone()],
49        Some(FieldValue::StringArray(arr)) => arr.clone(),
50        None => vec![],
51    }
52}
53
54/// Get the list of available options.
55pub fn get_options(tree: &FieldTree, id: FieldId) -> &[ChoiceOption] {
56    &tree.get(id).options
57}
58
59/// Set the selection for a single-select choice field.
60/// For non-editable combos, value must match an option. Returns `false` if read-only or invalid.
61pub fn set_selection(tree: &mut FieldTree, id: FieldId, value: &str) -> bool {
62    let flags = tree.effective_flags(id);
63    if flags.read_only() {
64        return false;
65    }
66    if choice_kind(flags) == ChoiceKind::ComboBox
67        && !tree
68            .get(id)
69            .options
70            .iter()
71            .any(|o| o.export == value || o.display == value)
72    {
73        return false;
74    }
75    tree.get_mut(id).value = Some(FieldValue::Text(value.to_string()));
76    true
77}
78
79/// Set multiple selections for a multi-select list box. Returns `false` if read-only or not multi-select.
80pub fn set_multi_selection(tree: &mut FieldTree, id: FieldId, values: Vec<String>) -> bool {
81    let flags = tree.effective_flags(id);
82    if flags.read_only() || !flags.multi_select() {
83        return false;
84    }
85    tree.get_mut(id).value = Some(FieldValue::StringArray(values));
86    true
87}
88
89/// Get the index of the first selected option, if any.
90pub fn selected_index(tree: &FieldTree, id: FieldId) -> Option<usize> {
91    let first = get_selection(tree, id).into_iter().next()?;
92    tree.get(id)
93        .options
94        .iter()
95        .position(|o| o.export == first || o.display == first)
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    fn make_choice_tree() -> (FieldTree, FieldId) {
102        let mut tree = FieldTree::new();
103        let id = tree.alloc(FieldNode {
104            partial_name: "dd".into(),
105            alternate_name: None,
106            mapping_name: None,
107            field_type: Some(FieldType::Choice),
108            flags: FieldFlags::from_bits(1 << 17),
109            value: None,
110            default_value: None,
111            default_appearance: None,
112            quadding: None,
113            max_len: None,
114            options: vec![
115                ChoiceOption {
116                    export: "a".into(),
117                    display: "Alpha".into(),
118                },
119                ChoiceOption {
120                    export: "b".into(),
121                    display: "Beta".into(),
122                },
123                ChoiceOption {
124                    export: "c".into(),
125                    display: "Gamma".into(),
126                },
127            ],
128            top_index: None,
129            rect: Some([0.0, 0.0, 150.0, 20.0]),
130            appearance_state: None,
131            on_state: None,
132            page_index: None,
133            parent: None,
134            children: vec![],
135            object_id: None,
136            has_actions: false,
137            mk: None,
138            border_style: None,
139        });
140        (tree, id)
141    }
142    #[test]
143    fn kind_combo() {
144        assert_eq!(
145            choice_kind(FieldFlags::from_bits(1 << 17)),
146            ChoiceKind::ComboBox
147        );
148    }
149    #[test]
150    fn kind_editable() {
151        assert_eq!(
152            choice_kind(FieldFlags::from_bits((1 << 17) | (1 << 18))),
153            ChoiceKind::EditableCombo
154        );
155    }
156    #[test]
157    fn kind_listbox() {
158        assert_eq!(choice_kind(FieldFlags::empty()), ChoiceKind::ListBox);
159    }
160    #[test]
161    fn kind_multi() {
162        assert_eq!(
163            choice_kind(FieldFlags::from_bits(1 << 21)),
164            ChoiceKind::MultiSelectListBox
165        );
166    }
167    #[test]
168    fn set_valid() {
169        let (mut tree, id) = make_choice_tree();
170        assert!(set_selection(&mut tree, id, "a"));
171        assert_eq!(get_selection(&tree, id), vec!["a"]);
172    }
173    #[test]
174    fn set_invalid() {
175        let (mut tree, id) = make_choice_tree();
176        assert!(!set_selection(&mut tree, id, "nope"));
177    }
178    #[test]
179    fn sel_index() {
180        let (mut tree, id) = make_choice_tree();
181        set_selection(&mut tree, id, "b");
182        assert_eq!(selected_index(&tree, id), Some(1));
183    }
184}