mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
M6: form buttons show the side panels (execMenuItem ShowHide…)
This commit is contained in:
@@ -657,6 +657,7 @@ fn request_json(r: &pdfcraft_engine::js::Request) -> Value {
|
||||
R::Focus(f) => json!({ "focus": f }),
|
||||
R::Beep => json!({ "beep": true }),
|
||||
R::SaveAs => json!({ "save_as": true }),
|
||||
R::ShowPanel(p) => json!({ "panel": format!("{p:?}").to_lowercase() }),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! off, scripts don't run and AF calls keep working natively.
|
||||
|
||||
use pdfcraft_forms::{Field, FieldChange, FieldEvent, FieldKind, FieldProps, ScriptResult, Scripts};
|
||||
pub use pdfcraft_js::{Limits, Outcome, Request};
|
||||
pub use pdfcraft_js::{Limits, Outcome, Panel, Request};
|
||||
|
||||
use crate::{DocId, Edit, EditError, Session};
|
||||
|
||||
@@ -29,7 +29,14 @@ impl JsOutput {
|
||||
fn absorb(&mut self, o: &Outcome) {
|
||||
self.alerts.extend(o.alerts.iter().cloned());
|
||||
self.console.extend(o.console.iter().cloned());
|
||||
self.requests.extend(o.requests.iter().cloned());
|
||||
// A field's own scripts (keystroke, format, validate, calculate) run unasked: only a
|
||||
// button's may show or hide a side panel.
|
||||
for r in &o.requests {
|
||||
match r {
|
||||
Request::ShowPanel(_) => self.console.push("A field script asked to show a side panel; only a button may show a side panel".into()),
|
||||
r => self.requests.push(r.clone()),
|
||||
}
|
||||
}
|
||||
self.errors.extend(o.error.iter().cloned());
|
||||
}
|
||||
|
||||
|
||||
@@ -6106,3 +6106,30 @@ fn undo_never_makes_an_unsaved_document_clean() {
|
||||
s.undo(new).unwrap();
|
||||
assert!(!s.get(new).unwrap().dirty, "once saved, undo back to the saved state is clean");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_xfa_button_may_show_a_side_panel_and_other_events_may_not() {
|
||||
let xdp = r##"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
|
||||
<subform name="form" layout="tb">
|
||||
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
|
||||
<field name="files" w="2in" h="0.3in"><ui><button/></ui><caption><value><text>Attachments</text></value></caption>
|
||||
<event activity="click"><script contentType="application/x-javascript">app.execMenuItem("ShowHideFileAttachment");</script></event></field>
|
||||
<field name="note" w="2in" h="0.3in"><ui><textEdit/></ui>
|
||||
<event activity="exit"><script contentType="application/x-javascript">app.execMenuItem("ShowHideFileAttachment");</script></event></field>
|
||||
</subform>
|
||||
</template>
|
||||
</xdp:xdp>
|
||||
"##;
|
||||
let mut s = Session::new();
|
||||
let id = s.open("files.pdf", None, Arc::new(pdfcraft_xfa::fixtures::shell(xdp)), None).expect("opens");
|
||||
s.take_js_output(id);
|
||||
s.apply(id, Edit::XfaEvent { som: "form[0].files[0]".into(), activity: "click".into() }).unwrap();
|
||||
let out = s.take_js_output(id);
|
||||
assert_eq!(out.requests, [pdfcraft_js::Request::ShowPanel(pdfcraft_js::Panel::Attachments)], "{out:?}");
|
||||
s.apply(id, Edit::SetFieldValue { name: "note".into(), value: FieldValue::Text("x".into()) }).unwrap();
|
||||
let out = s.take_js_output(id);
|
||||
assert!(out.requests.is_empty(), "only a click shows a panel: {out:?}");
|
||||
assert!(out.console.iter().any(|c| c.contains("show a side panel")), "{:?}", out.console);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ fn click_only(e: &XfaEffect) -> Option<&'static str> {
|
||||
XfaEffect::Print => Some("print"),
|
||||
XfaEffect::SaveAs => Some("save"),
|
||||
XfaEffect::LaunchUrl(_) => Some("open a link"),
|
||||
XfaEffect::ShowPanel(_) => Some("show a side panel"),
|
||||
XfaEffect::SetFocus(_) => Some("move the focus"),
|
||||
_ => None,
|
||||
}
|
||||
@@ -626,6 +627,7 @@ impl<'a, 'b> Runner<'a, 'b> {
|
||||
XfaEffect::Print => self.run.out.requests.push(Request::Print),
|
||||
XfaEffect::SaveAs => self.run.out.requests.push(Request::SaveAs),
|
||||
XfaEffect::LaunchUrl(u) => self.run.out.requests.push(Request::LaunchUrl(u.clone())),
|
||||
XfaEffect::ShowPanel(p) => self.run.out.requests.push(Request::ShowPanel(*p)),
|
||||
XfaEffect::SetFocus(som) => {
|
||||
if let Some(name) = self.by_som().get(som).cloned() {
|
||||
self.run.out.requests.push(Request::Focus(name));
|
||||
|
||||
+38
-1
@@ -13,7 +13,7 @@
|
||||
//! `required`, `display`, `hidden`, `numItems`, `getItemAt`, `currentValueIndices`,
|
||||
//! `isBoxChecked`, `checkThisBox`, `exportValues`, `charLimit`, `textColor`, `fillColor`,
|
||||
//! `setFocus`.
|
||||
//! - `app` (`alert`, `beep`, `response`, `launchURL`, `viewerType`, `viewerVersion`,
|
||||
//! - `app` (`alert`, `beep`, `response`, `launchURL`, `execMenuItem`, `viewerType`, `viewerVersion`,
|
||||
//! `platform`, `setTimeOut`), `util` (`printf`, `printd`, `printx`), `console` (`println`,
|
||||
//! `show`, `clear`), `display` and `color`.
|
||||
//!
|
||||
@@ -159,6 +159,19 @@ pub enum Request {
|
||||
Beep,
|
||||
/// `app.execMenuItem("SaveAs")` (XFA forms' Save buttons).
|
||||
SaveAs,
|
||||
/// `app.execMenuItem("ShowHide…")`: show the side panel, or hide it when it is showing.
|
||||
ShowPanel(Panel),
|
||||
}
|
||||
|
||||
/// A side panel a script can show or hide (`app.execMenuItem("ShowHideFileAttachment")`, …).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Panel {
|
||||
Bookmarks,
|
||||
/// Page thumbnails.
|
||||
Pages,
|
||||
Attachments,
|
||||
Signatures,
|
||||
Layers,
|
||||
}
|
||||
|
||||
/// What running a script did.
|
||||
@@ -565,6 +578,29 @@ fn launch_url(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsVa
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// What `app.execMenuItem(name)` asks for, for the menu items forms use: the side panels, Print
|
||||
/// and Save As. Other items do nothing.
|
||||
pub(crate) fn menu_item(name: &str) -> Option<Request> {
|
||||
Some(match name {
|
||||
"ShowHideFileAttachment" => Request::ShowPanel(Panel::Attachments),
|
||||
"ShowHideBookmarks" => Request::ShowPanel(Panel::Bookmarks),
|
||||
"ShowHideThumbnails" => Request::ShowPanel(Panel::Pages),
|
||||
"ShowHideSignatures" => Request::ShowPanel(Panel::Signatures),
|
||||
"ShowHideOptCont" => Request::ShowPanel(Panel::Layers),
|
||||
"Print" => Request::Print,
|
||||
"SaveAs" | "Save" => Request::SaveAs,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn exec_menu_item(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue> {
|
||||
let item = text(&arg(args, 0), ctx)?;
|
||||
if let Some(r) = menu_item(&item) {
|
||||
host(ctx)?.borrow_mut().requests.push(r);
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
fn response(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
|
||||
// No one to answer: as if the dialog was cancelled.
|
||||
Ok(JsValue::null())
|
||||
@@ -931,6 +967,7 @@ fn install(ctx: &mut Context) -> JsResult<()> {
|
||||
.function(NativeFunction::from_fn_ptr(beep), js_string!("beep"), 0)
|
||||
.function(NativeFunction::from_fn_ptr(response), js_string!("response"), 1)
|
||||
.function(NativeFunction::from_fn_ptr(launch_url), js_string!("launchURL"), 2)
|
||||
.function(NativeFunction::from_fn_ptr(exec_menu_item), js_string!("execMenuItem"), 1)
|
||||
.function(NativeFunction::from_fn_ptr(timer), js_string!("setTimeOut"), 2)
|
||||
.function(NativeFunction::from_fn_ptr(timer), js_string!("setInterval"), 2)
|
||||
.function(NativeFunction::from_fn_ptr(noop), js_string!("clearTimeOut"), 1)
|
||||
|
||||
@@ -1132,3 +1132,39 @@ mod session {
|
||||
assert!(o.abandoned || o.error.as_deref().is_some_and(|e| e.contains("too long")), "{o:?}");
|
||||
}
|
||||
}
|
||||
|
||||
mod menu_items {
|
||||
use super::*;
|
||||
use crate::xfa::{XfaDoc, XfaEffect, XfaEvent, run_xfa};
|
||||
|
||||
#[test]
|
||||
fn exec_menu_item_shows_side_panels_prints_and_saves() {
|
||||
let o = go(
|
||||
"app.execMenuItem('ShowHideFileAttachment'); app.execMenuItem('ShowHideBookmarks'); app.execMenuItem('ShowHideThumbnails'); \
|
||||
app.execMenuItem('ShowHideSignatures'); app.execMenuItem('ShowHideOptCont'); app.execMenuItem('Print'); \
|
||||
app.execMenuItem('SaveAs'); app.execMenuItem('NoSuchItem');",
|
||||
&Event::field("Mouse Up", "total", ""),
|
||||
);
|
||||
assert_eq!(o.error, None);
|
||||
assert_eq!(
|
||||
o.requests,
|
||||
[
|
||||
Request::ShowPanel(Panel::Attachments),
|
||||
Request::ShowPanel(Panel::Bookmarks),
|
||||
Request::ShowPanel(Panel::Pages),
|
||||
Request::ShowPanel(Panel::Signatures),
|
||||
Request::ShowPanel(Panel::Layers),
|
||||
Request::Print,
|
||||
Request::SaveAs,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xfa_scripts_show_the_attachments_panel() {
|
||||
let event = XfaEvent { activity: "click".into(), target: "form1[0].page1[0].go[0]".into(), ..Default::default() };
|
||||
let o = run_xfa("app.execMenuItem('ShowHideFileAttachment')", &event, &XfaDoc::default(), &super::xfa_model::form(), Limits::default());
|
||||
assert_eq!(o.error, None, "{o:?}");
|
||||
assert!(o.effects.contains(&XfaEffect::ShowPanel(Panel::Attachments)), "{:?}", o.effects);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,8 @@ pub enum XfaEffect {
|
||||
MessageBox(String),
|
||||
/// `xfa.host.resetData()`: the named fields, or every field when empty.
|
||||
ResetData(Vec<String>),
|
||||
/// `app.execMenuItem("ShowHide…")`: show or hide a side panel.
|
||||
ShowPanel(crate::Panel),
|
||||
Print,
|
||||
SaveAs,
|
||||
LaunchUrl(String),
|
||||
@@ -1049,9 +1051,10 @@ fn set_focus(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsVal
|
||||
|
||||
fn exec_menu_item(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue> {
|
||||
let item = text(&arg(args, 0), ctx)?;
|
||||
match item.as_str() {
|
||||
"SaveAs" | "Save" => effect(ctx, XfaEffect::SaveAs)?,
|
||||
"Print" => effect(ctx, XfaEffect::Print)?,
|
||||
match crate::menu_item(&item) {
|
||||
Some(crate::Request::SaveAs) => effect(ctx, XfaEffect::SaveAs)?,
|
||||
Some(crate::Request::Print) => effect(ctx, XfaEffect::Print)?,
|
||||
Some(crate::Request::ShowPanel(p)) => effect(ctx, XfaEffect::ShowPanel(p))?,
|
||||
_ => {}
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! the JavaScript console (⌘J), Document JavaScripts, and Preferences ▸ JavaScript.
|
||||
|
||||
use egui::{Align, Layout};
|
||||
use pdfcraft_engine::js::{JsOutput, Request};
|
||||
use pdfcraft_engine::js::{JsOutput, Panel, Request};
|
||||
use pdfcraft_engine::{DocId, Edit};
|
||||
|
||||
use crate::theme::{self, Tokens};
|
||||
@@ -67,6 +67,17 @@ impl PdfCraftApp {
|
||||
&[("u", &u)],
|
||||
),
|
||||
Request::SaveAs => self.run_command("file.save_as"),
|
||||
Request::ShowPanel(p) => {
|
||||
let panel = match p {
|
||||
Panel::Bookmarks => crate::RightPanel::Bookmarks,
|
||||
Panel::Pages => crate::RightPanel::Pages,
|
||||
Panel::Attachments => crate::RightPanel::Attachments,
|
||||
Panel::Signatures => crate::RightPanel::Signatures,
|
||||
Panel::Layers => crate::RightPanel::Layers,
|
||||
};
|
||||
// Show/Hide: a panel already showing closes.
|
||||
self.right = (self.right != Some(panel)).then_some(panel);
|
||||
}
|
||||
Request::Focus(_) | Request::Beep | Request::Reset(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1072,3 +1072,18 @@ fn auto_size_fields_shrink_and_wrap_typed_text_to_fit_their_box() {
|
||||
// An explicit size is kept, as in Acrobat: the text may then run past the box.
|
||||
assert_eq!(on_screen_fit(&h, "fixed", long).0, 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_form_button_shows_and_hides_the_attachments_panel() {
|
||||
let mut h = harness();
|
||||
h.state_mut().set_option("panel", "none").unwrap();
|
||||
h.run_steps(2);
|
||||
let id = h.state().views[0].id;
|
||||
h.state_mut().run_button_script(id, "size", "app.execMenuItem('ShowHideFileAttachment');");
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().right, Some(pdfcraft_ui_egui::RightPanel::Attachments));
|
||||
// Show/Hide, as its name says: asked again, it closes.
|
||||
h.state_mut().run_button_script(id, "size", "app.execMenuItem('ShowHideFileAttachment');");
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().right, None);
|
||||
}
|
||||
|
||||
@@ -5137,7 +5137,7 @@ status = "partial"
|
||||
commands = ["tools.js_console"]
|
||||
tools = ["js_run"]
|
||||
evidence = ["crates/js/src/tests.rs::calculate_scripts_read_fields_and_set_the_value", "crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats", "crates/automation/tests/automation.rs::javascript_through_tools"]
|
||||
notes = "boa (pure Rust) with the forms object model: event, getField/Field (value, readonly, display, colours, items, check boxes), app, util.printf/printd/printx, console, display, color; no annotations, layers or dialogs (app.response returns null) yet."
|
||||
notes = "boa (pure Rust) with the forms object model: event, getField/Field (value, readonly, display, colours, items, check boxes), app (with execMenuItem for the side panels, Print and Save As, from a button only), util.printf/printd/printx, console, display, color; no annotations, layers or dialogs (app.response returns null) yet."
|
||||
|
||||
[[feature]]
|
||||
id = "form.js-console"
|
||||
|
||||
Reference in New Issue
Block a user