fix(plot): parent PDF save dialog

Remove the temporary EXPORTPDF <path> fallback now that native save requests are tied to the main window.\n\nCloses #537
This commit is contained in:
Hakan Seven committed 2026-07-28 19:23:29 +03:00
1 parent d77eb8f419
commit 99dbc8254d
5 files changed
+66 -117

No files matched your search

-50
View File
@@ -821,56 +821,6 @@ mod tests {
}
}
#[test]
fn exportpdf_with_explicit_path_writes_pdf_without_dialog() {
// EXPORTPDF <path> must export straight to the given file — no save
// dialog — and confirm on the command line. This is the dialog-free
// export path added for #369 (Export to PDF silently did nothing when
// the native save dialog could not open).
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
app.automation_op(r#"{"op":"run","cmd":"LINE 0,0 100,50"}"#);
let path = std::env::temp_dir().join(format!("ocs_test_369_{}.pdf", std::process::id()));
let _ = std::fs::remove_file(&path);
let start = app.command_line.history.len();
let _ = app.run_command_line(&format!("EXPORTPDF {}", path.display()));
let out: String = app.command_line.history[start..]
.iter()
.map(|e| e.text.as_str())
.collect::<Vec<_>>()
.join("\n");
assert!(out.contains("Exported"), "no export confirmation: {out:?}");
let bytes = std::fs::read(&path).expect("EXPORTPDF <path> should write the file");
assert!(bytes.starts_with(b"%PDF"), "output is not a PDF");
assert!(bytes.len() > 200, "suspiciously small PDF: {}", bytes.len());
let _ = std::fs::remove_file(&path);
}
#[test]
fn cancelled_pdf_save_dialog_reports_on_command_line() {
// The save dialog resolving to None (cancelled, or it never opened —
// e.g. a broken XDG portal) must leave a visible message, not silence.
// Regression for #369.
use crate::app::Message;
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
for msg in [Message::PlotExportPath(None), Message::PlotWindowExportPath(None)] {
let start = app.command_line.history.len();
let _ = app.update(msg);
let out: String = app.command_line.history[start..]
.iter()
.map(|e| e.text.as_str())
.collect::<Vec<_>>()
.join("\n");
assert!(
out.contains("EXPORTPDF"),
"dialog-None must mention the EXPORTPDF fallback: {out:?}"
);
}
}
#[test]
fn handing_over_an_already_open_drawing_switches_to_its_tab() {
// Double-clicking a drawing that is already open should land on the tab
+2 -34
View File
@@ -448,40 +448,8 @@ impl OpenCADStudio {
"PLOT" | "PRINT" => {
return Some(Task::done(Message::PlotDialogOpen));
}
// With no argument these open the save dialog; with a path they
// export straight to it. The direct form is also the workaround for
// systems where the native save dialog cannot open at all (broken
// XDG portal / missing zenity on Linux) — there the dialog future
// resolves to None and no export would happen. (#369)
cmd if cmd == "EXPORT"
|| cmd == "EXPORTPDF"
|| cmd.starts_with("EXPORT ")
|| cmd.starts_with("EXPORTPDF ") =>
{
// Strip the keyword actually typed — EXPORTPDF first, since
// EXPORT is its prefix (see #295 for the failure mode).
let arg = cmd
.strip_prefix("EXPORTPDF")
.or_else(|| cmd.strip_prefix("EXPORT"))
.unwrap_or("")
.trim()
.trim_matches('"');
if arg.is_empty() {
return Some(Task::done(Message::PlotExport));
}
let mut path = std::path::PathBuf::from(arg);
if path.extension().is_none() {
path.set_extension("pdf");
}
// A bare filename lands next to the drawing when it has a home.
if path.is_relative() {
if let Some(dir) =
self.tabs[i].current_path.as_deref().and_then(|p| p.parent())
{
path = dir.join(path);
}
}
return Some(self.on_plot_export_path_some(path));
"EXPORT" | "EXPORTPDF" => {
return Some(Task::done(Message::PlotExport));
}
// PLOTSTYLE — load or clear CTB/STB plot style table
cmd if cmd == "PLOTSTYLE" || cmd.starts_with("PLOTSTYLE ") => {
+36 -28
View File
@@ -4356,23 +4356,25 @@ impl OpenCADStudio {
.and_then(|p: &std::path::Path| p.file_stem())
.map(|s: &std::ffi::OsStr| s.to_string_lossy().into_owned())
.unwrap_or_else(|| "drawing".into());
Task::perform(
crate::io::pdf_export::pick_pdf_path_owned(stem),
Message::PlotExportPath,
)
}
// None means the save dialog was cancelled — or never opened at
// all (a broken XDG portal / missing zenity on Linux resolves to
// None too, and rfd only reports that through `log`, which is
// opt-in). Either way say something instead of silently doing
// nothing. (#369)
Message::PlotExportPath(None) => {
self.command_line.push_info(
"PDF export canceled — no file chosen. \
If no dialog appeared, use EXPORTPDF <path>.",
);
Task::none()
#[cfg(not(target_arch = "wasm32"))]
{
let Some(window_id) = self.main_window else {
return Task::done(Message::PlotExportPath(None));
};
iced::window::run(window_id, move |parent| {
crate::io::pdf_export::pick_pdf_path_owned(stem, parent)
})
.map(Message::PlotExportPath)
}
#[cfg(target_arch = "wasm32")]
{
Task::perform(
crate::io::pdf_export::pick_pdf_path_owned(stem),
Message::PlotExportPath,
)
}
}
Message::PlotExportPath(None) => Task::none(),
Message::PlotExportPath(Some(path)) => self.on_plot_export_path_some(path),
Message::PlotFormat(f) => {
@@ -4391,19 +4393,25 @@ impl OpenCADStudio {
.and_then(|p: &std::path::Path| p.file_stem())
.map(|s: &std::ffi::OsStr| s.to_string_lossy().into_owned())
.unwrap_or_else(|| "drawing".into());
Task::perform(
crate::io::pdf_export::pick_pdf_path_owned(stem),
Message::PlotWindowExportPath,
)
}
// Same silent-None trap as PlotExportPath above. (#369)
Message::PlotWindowExportPath(None) => {
self.command_line.push_info(
"PDF export canceled — no file chosen. \
If no dialog appeared, use EXPORTPDF <path>.",
);
Task::none()
#[cfg(not(target_arch = "wasm32"))]
{
let Some(window_id) = self.main_window else {
return Task::done(Message::PlotWindowExportPath(None));
};
iced::window::run(window_id, move |parent| {
crate::io::pdf_export::pick_pdf_path_owned(stem, parent)
})
.map(Message::PlotWindowExportPath)
}
#[cfg(target_arch = "wasm32")]
{
Task::perform(
crate::io::pdf_export::pick_pdf_path_owned(stem),
Message::PlotWindowExportPath,
)
}
}
Message::PlotWindowExportPath(None) => Task::none(),
Message::PlotWindowExportPath(Some(path)) => self.on_plot_window_export_path_some(path),
Message::BackgroundIoFinished(result, reopen_plot) => {