forked from 1b-agent/opencadstudio-upstream
Merge pull request #1344 from HakanSeven12/plot/b3-psetupin
feat(plot): PSETUPIN, page-setup import and page setup for new layouts
This commit is contained in:
commit
b1080e02ca
35 files changed
+786
-48
No files matched your search
@@ -567,6 +567,8 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"BACKGROUNDPLOT",
|
||||
"CTAB",
|
||||
"TILEMODE",
|
||||
"PSETUPIN",
|
||||
"-PSETUPIN",
|
||||
"DISPSILH",
|
||||
"WORLDVIEW",
|
||||
"LIMCHECK",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
//! Plot system variables. `PLOTOFFSET`, `PAPERUPDATE`, `PLOTROTMODE`,
|
||||
//! `PLOTTRANSPARENCYOVERRIDE` and `BACKGROUNDPLOT` are plot preferences of
|
||||
//! the application, kept with the Plot dialog's persisted settings; `CTAB`
|
||||
//! and `TILEMODE` say which layout of the drawing is current. Each is
|
||||
//! typeable on its own (`PLOTOFFSET 1`, `CTAB Layout1`) and through
|
||||
//! `SETVAR`, and a bare name prompts for the value like the other variables.
|
||||
//! Plot system variables and the page-setup import command. `PLOTOFFSET`,
|
||||
//! `PAPERUPDATE`, `PLOTROTMODE`, `PLOTTRANSPARENCYOVERRIDE` and
|
||||
//! `BACKGROUNDPLOT` are plot preferences of the application, kept with the
|
||||
//! Plot dialog's persisted settings; `CTAB` and `TILEMODE` say which layout
|
||||
//! of the drawing is current. Each is typeable on its own (`PLOTOFFSET 1`,
|
||||
//! `CTAB Layout1`) and through `SETVAR`, and a bare name prompts for the
|
||||
//! value like the other variables. `PSETUPIN` brings named page setups in
|
||||
//! from another drawing (see `app::update::page_setup_import`).
|
||||
|
||||
use crate::app::{Message, OpenCADStudio};
|
||||
use iced::Task;
|
||||
@@ -39,6 +41,16 @@ enum Outcome {
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(super) fn dispatch_plotvars(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||
// PSETUPIN [file [* | name,name…]] — the dash form is the same
|
||||
// command; both read the file and report on the command line.
|
||||
for name in ["PSETUPIN", "-PSETUPIN"] {
|
||||
if cmd == name {
|
||||
return Some(self.on_psetupin(""));
|
||||
}
|
||||
if let Some(args) = cmd.strip_prefix(name).filter(|rest| rest.starts_with(' ')) {
|
||||
return Some(self.on_psetupin(args));
|
||||
}
|
||||
}
|
||||
let rest = cmd.strip_prefix("SETVAR ").map(str::trim).unwrap_or(cmd);
|
||||
let mut parts = rest.splitn(2, char::is_whitespace);
|
||||
let name = parts.next().unwrap_or("").to_ascii_uppercase();
|
||||
|
||||
@@ -2196,6 +2196,10 @@ pub enum Message {
|
||||
SaveTimeChanged(i32),
|
||||
/// Toggle keeping a `.bak` copy when overwriting a drawing (ISAVEBAK).
|
||||
BackupOnSaveChanged(bool),
|
||||
/// A drawing was picked to import page setups from (`PSETUPIN`).
|
||||
PageSetupImportFile(std::path::PathBuf),
|
||||
/// Options: open the Plot / Page Setup dialog for every new layout.
|
||||
PageSetupOnNewLayoutChanged(bool),
|
||||
/// Toggle filled TrueType glyphs (TEXTFILL).
|
||||
TextFillChanged(bool),
|
||||
/// Change how many prompt lines sit above the command window (CLIPROMPTLINES).
|
||||
|
||||
@@ -4443,6 +4443,7 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
M::PrinterProperties => self.on_printer_properties(),
|
||||
M::Import(msg) => self.on_page_setup_import(msg),
|
||||
M::PrinterOptionsLoaded(printer, result) => {
|
||||
if let Some(draft) = self.plot_dialog.printer_editor.as_mut() {
|
||||
if draft.printer == printer {
|
||||
@@ -4836,7 +4837,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_page_setups(&mut self) {
|
||||
pub(super) fn refresh_page_setups(&mut self) {
|
||||
use crate::ui::window::plot::{SETUP_NONE, SETUP_PREV};
|
||||
let scene = &self.tabs[self.active_tab].scene;
|
||||
// <none> / <previous>, then layouts (`*name*`), then named setups.
|
||||
@@ -4849,7 +4850,7 @@ impl OpenCADStudio {
|
||||
/// Apply a page-setup list selection to the editor. Handles the pseudo
|
||||
/// entries (`<none>` / `<previous>`), layout rows (`*name*`) and named
|
||||
/// setups.
|
||||
fn select_page_setup(&mut self, name: &str) {
|
||||
pub(super) fn select_page_setup(&mut self, name: &str) {
|
||||
use crate::ui::window::plot::{SETUP_NONE, SETUP_PREV};
|
||||
self.plot_dialog.selected_setup = name.to_string();
|
||||
if name == SETUP_NONE {
|
||||
|
||||
@@ -94,6 +94,7 @@ mod context_menu;
|
||||
mod dialog;
|
||||
mod dynamic;
|
||||
mod file;
|
||||
mod page_setup_import;
|
||||
mod style;
|
||||
mod util;
|
||||
mod viewport;
|
||||
@@ -7378,6 +7379,12 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PageSetupImportFile(path) => self.on_page_setup_import_file(path),
|
||||
Message::PageSetupOnNewLayoutChanged(enabled) => {
|
||||
self.plot_dialog.page_setup_on_new_layout = enabled;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
Message::BackupOnSaveChanged(enabled) => {
|
||||
self.backup_on_save = enabled;
|
||||
self.persist_settings_if_changed();
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
//! Importing named page setups from another drawing (`PSETUPIN`): the
|
||||
//! "Import…" chooser of the Plot dialog and the command that drives the same
|
||||
//! path from the command line.
|
||||
|
||||
use crate::app::{Message, OpenCADStudio};
|
||||
use crate::ui::window::plot::{PageSetupImportDraft, PageSetupImportMsg, PlotDlgMsg};
|
||||
use iced::Task;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Read the named page setups of the drawing at `path` off the UI thread.
|
||||
/// The result names the file the way the chooser shows it.
|
||||
fn read_page_setups_task(path: PathBuf) -> Task<Message> {
|
||||
super::file::background_task(
|
||||
move || {
|
||||
let file = path
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| path.display().to_string());
|
||||
crate::io::load_file(&path)
|
||||
.map(|document| (file, crate::scene::document_page_setups(&document)))
|
||||
},
|
||||
|result| Message::PlotDlg(PlotDlgMsg::Import(PageSetupImportMsg::Loaded(result))),
|
||||
)
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(super) fn on_page_setup_import(&mut self, msg: PageSetupImportMsg) -> Task<Message> {
|
||||
match msg {
|
||||
PageSetupImportMsg::Pick => Task::perform(
|
||||
async {
|
||||
let handle = rfd::AsyncFileDialog::new()
|
||||
.set_title(crate::t!("Select Drawing to Import Page Setups From").as_ref())
|
||||
.add_filter(
|
||||
crate::t!("Drawings and templates").as_ref(),
|
||||
&["dwg", "dxf", "dwt", "DWG", "DXF", "DWT"],
|
||||
)
|
||||
.pick_file()
|
||||
.await;
|
||||
handle.map(|h| crate::sys::handle_path(&h))
|
||||
},
|
||||
|picked| match picked {
|
||||
Some(path) => Message::PageSetupImportFile(path),
|
||||
None => Message::Noop,
|
||||
},
|
||||
),
|
||||
PageSetupImportMsg::Loaded(result) => {
|
||||
self.plot_dialog.import_draft = Some(match result {
|
||||
Ok((file, setups)) => PageSetupImportDraft {
|
||||
file,
|
||||
setups: setups.into_iter().map(|(name, ps)| (name, ps, true)).collect(),
|
||||
error: None,
|
||||
},
|
||||
Err(error) => PageSetupImportDraft {
|
||||
file: String::new(),
|
||||
setups: Vec::new(),
|
||||
error: Some(error),
|
||||
},
|
||||
});
|
||||
Task::none()
|
||||
}
|
||||
PageSetupImportMsg::Toggle(name) => {
|
||||
if let Some(draft) = self.plot_dialog.import_draft.as_mut() {
|
||||
if let Some(entry) = draft.setups.iter_mut().find(|(n, _, _)| *n == name) {
|
||||
entry.2 = !entry.2;
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
PageSetupImportMsg::All(on) => {
|
||||
if let Some(draft) = self.plot_dialog.import_draft.as_mut() {
|
||||
for entry in &mut draft.setups {
|
||||
entry.2 = on;
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
PageSetupImportMsg::Apply => {
|
||||
let Some(draft) = self.plot_dialog.import_draft.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
let chosen: Vec<(String, acadrust::objects::PlotSettings)> = draft
|
||||
.selected()
|
||||
.map(|(name, ps)| (name.to_string(), ps.clone()))
|
||||
.collect();
|
||||
let imported = self.import_page_setups(chosen);
|
||||
if let Some(first) = imported.first() {
|
||||
let first = first.clone();
|
||||
self.select_page_setup(&first);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
PageSetupImportMsg::Cancel => {
|
||||
self.plot_dialog.import_draft = None;
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A drawing was picked for import: read it in the background; the
|
||||
/// chooser appears in the Plot dialog once it is read.
|
||||
pub(super) fn on_page_setup_import_file(&mut self, path: PathBuf) -> Task<Message> {
|
||||
// The chooser lives in the Plot dialog; open it if the import was
|
||||
// started from the command line.
|
||||
let open = if self.active_modal == Some(crate::app::ModalKind::Plot) {
|
||||
Task::none()
|
||||
} else {
|
||||
self.on_plot_dialog_open()
|
||||
};
|
||||
Task::batch([open, read_page_setups_task(path)])
|
||||
}
|
||||
|
||||
/// Bring the given setups into the drawing, replacing same-named ones,
|
||||
/// as one undoable step. Returns the names imported.
|
||||
pub(super) fn import_page_setups(
|
||||
&mut self,
|
||||
setups: Vec<(String, acadrust::objects::PlotSettings)>,
|
||||
) -> Vec<String> {
|
||||
if setups.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
self.push_undo_snapshot(i, "PSETUPIN");
|
||||
let mut replaced = 0usize;
|
||||
let existing = self.tabs[i].scene.page_setup_names();
|
||||
let mut names = Vec::with_capacity(setups.len());
|
||||
for (name, ps) in setups {
|
||||
if existing.iter().any(|n| n.eq_ignore_ascii_case(&name)) {
|
||||
replaced += 1;
|
||||
}
|
||||
self.tabs[i].scene.import_page_setup(&name, ps);
|
||||
names.push(name);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_page_setups();
|
||||
let count = names.len();
|
||||
self.command_line.push_output(
|
||||
crate::tf!("PSETUPIN: {count} page setup(s) imported, {replaced} replaced.").as_ref(),
|
||||
);
|
||||
names
|
||||
}
|
||||
|
||||
/// `PSETUPIN` / `-PSETUPIN`: with no file, ask for one and offer its
|
||||
/// setups in the Plot dialog; with `<file> *` or `<file> name,name`
|
||||
/// import those straight away; with just `<file>` open the chooser on it.
|
||||
pub(in crate::app) fn on_psetupin(&mut self, args: &str) -> Task<Message> {
|
||||
let args = args.trim();
|
||||
if args.is_empty() {
|
||||
return self.on_page_setup_import(PageSetupImportMsg::Pick);
|
||||
}
|
||||
// The file may carry spaces; the setup list, when given, is the last
|
||||
// word (`*` or a comma-separated list without spaces), so the
|
||||
// arguments are a file followed by names only when what precedes the
|
||||
// last word is itself a file.
|
||||
let (file, names) = match args.rsplit_once(char::is_whitespace) {
|
||||
Some((file, names)) if Path::new(file.trim()).is_file() => {
|
||||
(file.trim(), Some(names.trim()))
|
||||
}
|
||||
_ => (args, None),
|
||||
};
|
||||
let path = PathBuf::from(file);
|
||||
let Some(names) = names else {
|
||||
return self.on_page_setup_import_file(path);
|
||||
};
|
||||
let available = match crate::io::load_file(&path) {
|
||||
Ok(document) => crate::scene::document_page_setups(&document),
|
||||
Err(error) => {
|
||||
self.command_line
|
||||
.push_error(crate::tf!("PSETUPIN: {error}").as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
let chosen: Vec<(String, acadrust::objects::PlotSettings)> = if names == "*" {
|
||||
available
|
||||
} else {
|
||||
let wanted: Vec<&str> = names.split(',').map(str::trim).collect();
|
||||
let mut chosen = Vec::new();
|
||||
for want in wanted {
|
||||
match available.iter().find(|(n, _)| n.eq_ignore_ascii_case(want)) {
|
||||
Some(found) => chosen.push(found.clone()),
|
||||
None => self.command_line.push_error(
|
||||
crate::tf!("PSETUPIN: no page setup named \"{want}\" in {file}.")
|
||||
.as_ref(),
|
||||
),
|
||||
}
|
||||
}
|
||||
chosen
|
||||
};
|
||||
if chosen.is_empty() {
|
||||
self.command_line
|
||||
.push_info(crate::tf!("PSETUPIN: nothing to import from {file}.").as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
self.import_page_setups(chosen);
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
mod tests {
|
||||
use crate::app::{Message, OpenCADStudio};
|
||||
use crate::ui::window::plot::{PageSetupImportMsg as I, PlotDlgMsg};
|
||||
|
||||
const FIXTURE: &str = concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/fixtures/plot/page-setups-metric.dxf"
|
||||
);
|
||||
|
||||
fn app() -> OpenCADStudio {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
let _ = app.update(Message::LayoutSwitch("Layout1".into()));
|
||||
app
|
||||
}
|
||||
|
||||
fn last_line(app: &OpenCADStudio) -> String {
|
||||
app.command_line.history.last().map(|line| line.text.clone()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The fixture's page setups as the chooser would hold them, read
|
||||
/// through the same document reader the command uses.
|
||||
fn fixture_setups() -> Vec<(String, acadrust::objects::PlotSettings)> {
|
||||
let document = crate::io::load_file(std::path::Path::new(FIXTURE)).unwrap();
|
||||
crate::scene::document_page_setups(&document)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_fixture_reader_lists_its_named_page_setups() {
|
||||
// The fixture carries its page setups on the layouts, not as named
|
||||
// setups; a drawing with named ones lists them in dictionary order.
|
||||
assert!(fixture_setups().is_empty());
|
||||
let mut app = app();
|
||||
let i = app.active_tab;
|
||||
let ps = app.tabs[i].scene.plot_settings_for("Layout1").unwrap();
|
||||
app.tabs[i].scene.page_setup_save("Site", ps.clone());
|
||||
app.tabs[i].scene.page_setup_save("Detail", ps);
|
||||
let names: Vec<String> = crate::scene::document_page_setups(&app.tabs[i].scene.document)
|
||||
.into_iter()
|
||||
.map(|(name, _)| name)
|
||||
.collect();
|
||||
assert_eq!(names, ["Site", "Detail"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn psetupin_imports_named_setups_from_a_saved_drawing() {
|
||||
use acadrust::objects::PlotRotation;
|
||||
// Author a source drawing with two named setups and save it.
|
||||
let dir = std::env::temp_dir().join(format!("ocs-psetupin-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let source = dir.join("source.dxf");
|
||||
{
|
||||
let mut app = app();
|
||||
let i = app.active_tab;
|
||||
let mut ps = app.tabs[i].scene.plot_settings_for("Layout1").unwrap();
|
||||
ps.printer_name = "DWG To PDF.pc3".into();
|
||||
ps.paper_size = "ISO_A3_(297.00_x_420.00_MM)".into();
|
||||
ps.paper_width = 297.0;
|
||||
ps.paper_height = 420.0;
|
||||
ps.rotation = PlotRotation::Degrees90;
|
||||
ps.plot_view_handle = acadrust::Handle::new(0xBEEF);
|
||||
app.tabs[i].scene.page_setup_save("Site", ps.clone());
|
||||
ps.paper_size = "ISO_A2_(420.00_x_594.00_MM)".into();
|
||||
app.tabs[i].scene.page_setup_save("Overview", ps);
|
||||
let save = format!(r#"{{"op":"save","path":"{}"}}"#, source.display());
|
||||
let reply = app.automation_op(&save);
|
||||
assert_eq!(reply["ok"], true, "{reply}");
|
||||
}
|
||||
let mut app = app();
|
||||
let i = app.active_tab;
|
||||
// One named setup already exists under a name the file also has.
|
||||
let own = app.tabs[i].scene.plot_settings_for("Layout1").unwrap();
|
||||
app.tabs[i].scene.page_setup_save("Site", own);
|
||||
// `*` imports everything, replacing the same-named one.
|
||||
let _ = app.run_command_line(&format!("PSETUPIN {} *", source.display()));
|
||||
// The report is translated on localized systems; the counts are not.
|
||||
let report = last_line(&app);
|
||||
assert!(report.starts_with("PSETUPIN"), "{report}");
|
||||
assert!(report.contains('2') && report.contains('1'), "{report}");
|
||||
let names = app.tabs[i].scene.page_setup_names();
|
||||
assert_eq!(names, ["Site", "Overview"]);
|
||||
let site = app.tabs[i].scene.page_setup_get("Site").unwrap();
|
||||
assert_eq!(site.paper_size, "ISO_A3_(297.00_x_420.00_MM)");
|
||||
assert_eq!(site.rotation, PlotRotation::Degrees90);
|
||||
assert!(site.plot_view_handle.is_null(), "foreign handles are dropped");
|
||||
assert!(app.tabs[i].dirty);
|
||||
// Undo takes the whole import back.
|
||||
let _ = app.update(Message::Undo);
|
||||
let names = app.tabs[i].scene.page_setup_names();
|
||||
assert_eq!(names, ["Site"]);
|
||||
assert_ne!(
|
||||
app.tabs[i].scene.page_setup_get("Site").unwrap().paper_size,
|
||||
"ISO_A3_(297.00_x_420.00_MM)"
|
||||
);
|
||||
// A name list imports just those and reports the unknown ones.
|
||||
let _ = app.run_command_line(&format!("PSETUPIN {} Overview,Nothing", source.display()));
|
||||
assert_eq!(app.tabs[i].scene.page_setup_names(), ["Site", "Overview"]);
|
||||
// A missing file is an error, not a crash.
|
||||
let _ = app.run_command_line("PSETUPIN /nowhere/at/all.dwg *");
|
||||
assert!(last_line(&app).starts_with("PSETUPIN:"));
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_chooser_imports_the_ticked_setups_and_selects_the_first() {
|
||||
let mut app = app();
|
||||
let i = app.active_tab;
|
||||
let ps = app.tabs[i].scene.plot_settings_for("Layout1").unwrap();
|
||||
let _ = app.on_plot_dialog_open();
|
||||
let setups = ["A", "B", "C"]
|
||||
.into_iter()
|
||||
.map(|name| (name.to_string(), ps.clone()))
|
||||
.collect();
|
||||
let loaded = Ok(("other.dwg".to_string(), setups));
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Loaded(loaded)));
|
||||
let draft = app.plot_dialog.import_draft.as_ref().expect("chooser open");
|
||||
assert_eq!(draft.file, "other.dwg");
|
||||
assert_eq!(draft.selected().count(), 3, "everything ticked to start with");
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::All(false)));
|
||||
assert_eq!(app.plot_dialog.import_draft.as_ref().unwrap().selected().count(), 0);
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Toggle("B".into())));
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Toggle("C".into())));
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Apply));
|
||||
assert!(app.plot_dialog.import_draft.is_none(), "Apply closes the chooser");
|
||||
assert_eq!(app.tabs[i].scene.page_setup_names(), ["B", "C"]);
|
||||
assert_eq!(app.plot_dialog.selected_setup, "B");
|
||||
assert!(app.plot_dialog.page_setups.iter().any(|name| name == "C"));
|
||||
// A failed read shows its reason in the chooser; Cancel closes it.
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Loaded(Err("unreadable".into()))));
|
||||
assert_eq!(
|
||||
app.plot_dialog.import_draft.as_ref().unwrap().error.as_deref(),
|
||||
Some("unreadable")
|
||||
);
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::Import(I::Cancel));
|
||||
assert!(app.plot_dialog.import_draft.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_layout_opens_its_page_setup_when_the_option_is_on() {
|
||||
let mut app = app();
|
||||
let _ = app.update(Message::LayoutCreate);
|
||||
assert_ne!(app.active_modal, Some(crate::app::ModalKind::Plot));
|
||||
let _ = app.update(Message::PageSetupOnNewLayoutChanged(true));
|
||||
assert!(app.current_config().plot.page_setup_on_new_layout, "persisted");
|
||||
let _ = app.update(Message::LayoutCreate);
|
||||
assert_eq!(app.active_modal, Some(crate::app::ModalKind::Plot));
|
||||
assert_eq!(app.plot_dialog.selected_setup, "*Layout3*");
|
||||
}
|
||||
}
|
||||
@@ -6047,6 +6047,12 @@ properties={:.1}ms picked={}",
|
||||
crate::tf!("Layout \"{new_name}\" created — use MVIEW to add a viewport")
|
||||
.as_ref(),
|
||||
);
|
||||
// The option that greets every new layout with its page setup.
|
||||
let switch_task = if self.plot_dialog.page_setup_on_new_layout {
|
||||
Task::batch([switch_task, self.on_plot_dialog_open()])
|
||||
} else {
|
||||
switch_task
|
||||
};
|
||||
self.tabs[i].dirty = true;
|
||||
return Task::batch([cancel_task, switch_task]);
|
||||
}
|
||||
|
||||
@@ -289,6 +289,7 @@ impl OpenCADStudio {
|
||||
crate::ui::window::options::AppPrefs {
|
||||
savetime_min: self.savetime_min,
|
||||
backup_on_save: self.backup_on_save,
|
||||
page_setup_on_new_layout: self.plot_dialog.page_setup_on_new_layout,
|
||||
textfill: crate::scene::text::sdf_atlas::textfill(),
|
||||
cliprompt_lines: self.cliprompt_lines,
|
||||
commandline_fade_ms: self.commandline_fade_ms,
|
||||
|
||||
Reference in new issue
Block a user