mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-30 21:17:55 -06:00
M6: a list box's top row is chosen after the edit's new font size
set_props chose /TI inside the field-dictionary update, from the field read before the edit, so a font size or look changed in the same call was not counted: at a larger size fewer rows fit and the selection could end up below the visible rows. The field and widget top rows are now chosen after every layout change, from the field read again, before the redraw. Test: a_list_boxs_top_row_follows_a_font_size_changed_with_its_items (fails with the old ordering: rows 1..2 shown, selection on row 3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7a6177759c
commit
58c50f60d6
@@ -235,15 +235,7 @@ pub fn set_props(doc: &mut Document, name: &str, props: &FieldProps) -> Result<S
|
||||
if let Some(exports) = &new_exports
|
||||
&& matches!(f.kind, FieldKind::Combo | FieldKind::List)
|
||||
{
|
||||
let picked = repoint_selection(d, &f.value, exports);
|
||||
// A list box scrolls, as for a new value, so its first row is still an option and
|
||||
// the selection is in view.
|
||||
if let Some((old, new)) = crate::values::list_scroll(&f, &picked, exports.len())
|
||||
&& old != new
|
||||
{
|
||||
d.set(b"TI".to_vec(), Object::Int(i64::try_from(new).unwrap_or(0)));
|
||||
}
|
||||
list_picked = Some((picked, exports.len()));
|
||||
list_picked = Some((repoint_selection(d, &f.value, exports), exports.len()));
|
||||
}
|
||||
if let Some(q) = props.quadding {
|
||||
d.set(b"Q".to_vec(), Object::Int(q));
|
||||
@@ -261,9 +253,6 @@ pub fn set_props(doc: &mut Document, name: &str, props: &FieldProps) -> Result<S
|
||||
d.set(b"DA".to_vec(), PdfString::literal(new_da.clone().into_bytes()));
|
||||
}
|
||||
})?;
|
||||
if let Some((picked, count)) = list_picked {
|
||||
crate::values::scroll_widgets(doc, &f, &picked, count)?;
|
||||
}
|
||||
if props.appearance.is_some() || (props.font_size.is_some() && props.look.is_none()) {
|
||||
let patch = &props.appearance.unwrap_or_default();
|
||||
for w in &f.widgets {
|
||||
@@ -421,6 +410,18 @@ pub fn set_props(doc: &mut Document, name: &str, props: &FieldProps) -> Result<S
|
||||
}
|
||||
// A label-only edit must not replace another widget's custom appearances.
|
||||
let only = props.caption.as_ref().filter(|_| *props == (FieldProps { caption: props.caption.clone(), ..Default::default() })).map(|(wi, _)| *wi);
|
||||
// A list box scrolls, as for a new value, so its first row is still an option and the
|
||||
// selection is in view. Its rows are measured with the edited font size and rectangle.
|
||||
if let Some((picked, count)) = list_picked
|
||||
&& let Some(g) = fields(doc).into_iter().find(|g| g.obj == f.obj)
|
||||
{
|
||||
if let Some((old, new)) = crate::values::list_scroll(&g, &picked, count)
|
||||
&& old != new
|
||||
{
|
||||
doc.update_dict(f.obj, |d| d.set(b"TI".to_vec(), Object::Int(i64::try_from(new).unwrap_or(0))))?;
|
||||
}
|
||||
crate::values::scroll_widgets(doc, &g, &picked, count)?;
|
||||
}
|
||||
redraw_widgets(doc, &new_name, only)?;
|
||||
if props.calculate.is_some() {
|
||||
crate::recalculate(doc)?;
|
||||
|
||||
@@ -4663,6 +4663,28 @@ fn each_widget_with_its_own_top_row_scrolls_by_itself() {
|
||||
assert_eq!(top_row(&reopen(&doc), other), Some(0), "value: the other widget scrolls to A");
|
||||
}
|
||||
|
||||
/// A list box's top row is chosen with the font size set in the same edit: at the larger size
|
||||
/// fewer rows fit, so it scrolls further to keep the selection in view.
|
||||
#[test]
|
||||
fn a_list_boxs_top_row_follows_a_font_size_changed_with_its_items() {
|
||||
let mut doc = document(&[
|
||||
"<< /Type /Catalog /Pages 2 0 R /AcroForm 4 0 R >>".into(),
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 600 800] >>".into(),
|
||||
"<< /Type /Page /Parent 2 0 R /Annots [6 0 R] >>".into(),
|
||||
"<< /Fields [6 0 R] /DA (/Helv 10 Tf 0 g) /DR << /Font << /Helv 5 0 R >> >> >>".into(),
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>".into(),
|
||||
"<< /FT /Ch /T (pick) /Opt [(A) (B) (C) (D) (E)] /V (C) /I [2] /Type /Annot /Subtype /Widget /Rect [50 700 250 740] /P 3 0 R >>".into(),
|
||||
]);
|
||||
let items = ["Z", "A", "B", "C", "D"].map(String::from).to_vec();
|
||||
set_props(&mut doc, "pick", &FieldProps { options: Some(items), font_size: Some(20.0), ..FieldProps::default() }).unwrap();
|
||||
let doc = reopen(&doc);
|
||||
let g = field(&fields(&doc), "pick").clone();
|
||||
let w = &g.widgets[0];
|
||||
let rows = w.list.unwrap();
|
||||
let shown = rows.top..rows.top + rows.visible(w.rect[3] - w.rect[1]);
|
||||
assert!(shown.contains(&3), "C is row 3; rows {shown:?} show");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sorting_a_combo_boxs_items_repoints_its_selected_index() {
|
||||
let mut doc = one_page();
|
||||
|
||||
@@ -4991,7 +4991,7 @@ milestone = "M6"
|
||||
status = "shipped"
|
||||
commands = ["form.field.properties"]
|
||||
tools = ["form_set_props"]
|
||||
evidence = ["crates/forms/src/tests.rs::button_caption_preserves_other_widgets_and_shared_dictionaries", "crates/forms/src/tests.rs::a_button_caption_is_drawn_in_its_fields_font_size_and_color", "crates/ui-egui/tests/it/prepare.rs::button_label_is_editable_without_renaming_the_field", "crates/automation/tests/it/button_caption.rs::button_caption_can_be_changed_after_creation", "crates/forms/src/tests.rs::properties_rename_and_delete", "crates/automation/tests/it/automation.rs::preparing_a_form_through_tools", "crates/forms/src/tests.rs::options_tab_flags_alignment_and_defaults", "crates/ui-egui/tests/it/prepare.rs::options_tab_sets_flags_alignment_and_defaults", "crates/automation/tests/it/form_bulk.rs::bulk_options_apply_per_field_and_refuse_an_invalid_comb_atomically", "crates/forms/src/tests.rs::changing_a_lists_options_repoints_its_selected_indices", "crates/forms/src/tests.rs::shrinking_a_lists_options_scrolls_a_widget_with_its_own_top_row", "crates/forms/src/tests.rs::each_widget_with_its_own_top_row_scrolls_by_itself", "crates/forms/src/tests.rs::sorting_a_combo_boxs_items_repoints_its_selected_index", "crates/automation/tests/it/automation.rs::changing_a_lists_options_through_tools_keeps_its_saved_selection_on_the_same_items"]
|
||||
evidence = ["crates/forms/src/tests.rs::button_caption_preserves_other_widgets_and_shared_dictionaries", "crates/forms/src/tests.rs::a_button_caption_is_drawn_in_its_fields_font_size_and_color", "crates/ui-egui/tests/it/prepare.rs::button_label_is_editable_without_renaming_the_field", "crates/automation/tests/it/button_caption.rs::button_caption_can_be_changed_after_creation", "crates/forms/src/tests.rs::properties_rename_and_delete", "crates/automation/tests/it/automation.rs::preparing_a_form_through_tools", "crates/forms/src/tests.rs::options_tab_flags_alignment_and_defaults", "crates/ui-egui/tests/it/prepare.rs::options_tab_sets_flags_alignment_and_defaults", "crates/automation/tests/it/form_bulk.rs::bulk_options_apply_per_field_and_refuse_an_invalid_comb_atomically", "crates/forms/src/tests.rs::changing_a_lists_options_repoints_its_selected_indices", "crates/forms/src/tests.rs::shrinking_a_lists_options_scrolls_a_widget_with_its_own_top_row", "crates/forms/src/tests.rs::each_widget_with_its_own_top_row_scrolls_by_itself", "crates/forms/src/tests.rs::a_list_boxs_top_row_follows_a_font_size_changed_with_its_items", "crates/forms/src/tests.rs::sorting_a_combo_boxs_items_repoints_its_selected_index", "crates/automation/tests/it/automation.rs::changing_a_lists_options_through_tools_keeps_its_saved_selection_on_the_same_items"]
|
||||
notes = "push buttons: per-widget normal label independent of the field name, including clearing and save/reopen, drawn in the font, size and color of the button's /DA (the form's /DR font, else Helvetica; an automatic size fits the button) and centered by Helvetica's widths; icon-only layout retained, rollover/down labels and icon layout editing remain unavailable; text: alignment, default, multi-line, scroll, rich text flag, limit, password, file selection, spell check, comb; choices: items (the selected indices /I follow their items when the list is edited or sorted), default, sort, custom text, multiple selection, commit immediately; check boxes and radios: checked by default, radios in unison. Shared GUI offers applicable text/choice flags, alignment, text limits and check styles; choices/defaults remain individual GUI edits"
|
||||
|
||||
[[feature]]
|
||||
|
||||
Reference in New Issue
Block a user