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            page_index: None,
132            parent: None,
133            children: vec![],
134            object_id: None,
135            has_actions: false,
136            mk: None,
137            border_style: None,
138        });
139        (tree, id)
140    }
141    #[test]
142    fn kind_combo() {
143        assert_eq!(
144            choice_kind(FieldFlags::from_bits(1 << 17)),
145            ChoiceKind::ComboBox
146        );
147    }
148    #[test]
149    fn kind_editable() {
150        assert_eq!(
151            choice_kind(FieldFlags::from_bits((1 << 17) | (1 << 18))),
152            ChoiceKind::EditableCombo
153        );
154    }
155    #[test]
156    fn kind_listbox() {
157        assert_eq!(choice_kind(FieldFlags::empty()), ChoiceKind::ListBox);
158    }
159    #[test]
160    fn kind_multi() {
161        assert_eq!(
162            choice_kind(FieldFlags::from_bits(1 << 21)),
163            ChoiceKind::MultiSelectListBox
164        );
165    }
166    #[test]
167    fn set_valid() {
168        let (mut tree, id) = make_choice_tree();
169        assert!(set_selection(&mut tree, id, "a"));
170        assert_eq!(get_selection(&tree, id), vec!["a"]);
171    }
172    #[test]
173    fn set_invalid() {
174        let (mut tree, id) = make_choice_tree();
175        assert!(!set_selection(&mut tree, id, "nope"));
176    }
177    #[test]
178    fn sel_index() {
179        let (mut tree, id) = make_choice_tree();
180        set_selection(&mut tree, id, "b");
181        assert_eq!(selected_index(&tree, id), Some(1));
182    }
183}