forked from 1b-agent/opencadstudio-upstream
Merge remote-tracking branch 'origin/main' into codex/pdf-attach-parity
# Conflicts: # locales/ar-SA/opencadstudio.ftl # locales/bg-BG/opencadstudio.ftl # locales/cs-CZ/opencadstudio.ftl # locales/de-DE/opencadstudio.ftl # locales/el-GR/opencadstudio.ftl # locales/en-US/opencadstudio.ftl # locales/es-ES/opencadstudio.ftl # locales/fi-FI/opencadstudio.ftl # locales/fr-FR/opencadstudio.ftl # locales/hi-IN/opencadstudio.ftl # locales/hu-HU/opencadstudio.ftl # locales/it-IT/opencadstudio.ftl # locales/ja-JP/opencadstudio.ftl # locales/ko-KR/opencadstudio.ftl # locales/nl-NL/opencadstudio.ftl # locales/pl-PL/opencadstudio.ftl # locales/pt-BR/opencadstudio.ftl # locales/ru-RU/opencadstudio.ftl # locales/tr-TR/opencadstudio.ftl # locales/zh-CN/opencadstudio.ftl # locales/zh-TW/opencadstudio.ftl # src/app/control/actions.rs # src/app/mod.rs # src/app/update/mod.rs # src/app/view/modal.rs # src/io/xref.rs # src/ui/window/mod.rs
This commit is contained in:
commit
c1ecb0edaf
77 files changed
+5695
-606
No files matched your search
+106
-13
@@ -460,6 +460,41 @@ impl OpenCADStudio {
|
||||
self.active_modal = Some(crate::app::ModalKind::BlockDefinition);
|
||||
}
|
||||
|
||||
cmd if cmd.starts_with("WBLOCK_POINT_PICKED ") => {
|
||||
let parts: Vec<f64> = cmd["WBLOCK_POINT_PICKED ".len()..]
|
||||
.split_whitespace()
|
||||
.filter_map(|s| s.parse::<f64>().ok())
|
||||
.collect();
|
||||
if parts.len() == 3 {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.base_point_x = format!("{:.4}", parts[0]);
|
||||
state.base_point_y = format!("{:.4}", parts[1]);
|
||||
state.base_point_z = format!("{:.4}", parts[2]);
|
||||
}
|
||||
}
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
|
||||
}
|
||||
|
||||
"WBLOCK_POINT_CANCELLED" => {
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
|
||||
}
|
||||
|
||||
"WBLOCK_OBJECTS_GATHERED" => {
|
||||
let handles: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.map(|(h, _)| h)
|
||||
.collect();
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.selected_handles = handles;
|
||||
}
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
|
||||
}
|
||||
|
||||
"INSERT" => {
|
||||
let blocks = self.tabs[i].scene.custom_block_names();
|
||||
if blocks.is_empty() {
|
||||
@@ -718,9 +753,56 @@ impl OpenCADStudio {
|
||||
// Launch the file picker; XAttachPickResult will start the command.
|
||||
return Some(Task::done(Message::XAttachPick));
|
||||
}
|
||||
cmd if cmd == "WBLOCK" || cmd == "WB" || cmd.starts_with("WBLOCK ") => {
|
||||
let arg = cmd.splitn(2, ' ').nth(1).unwrap_or("").trim();
|
||||
if arg.is_empty() {
|
||||
"WBLOCK" | "WB" => {
|
||||
let handles: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.map(|(h, _)| h)
|
||||
.collect();
|
||||
let existing_blocks = self.tabs[i].scene.custom_block_names();
|
||||
let default_unit = self.tabs[i].scene.document.header.insertion_units;
|
||||
let default_folder = self.tabs[i]
|
||||
.current_path
|
||||
.as_ref()
|
||||
.and_then(|p| p.parent())
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
let default_path = if default_folder.is_empty() {
|
||||
"new_block.dwg".to_string()
|
||||
} else {
|
||||
format!("{}/new_block.dwg", default_folder.replace('\\', "/"))
|
||||
};
|
||||
self.wblock = Some(
|
||||
crate::ui::window::wblock::WblockState::new(
|
||||
existing_blocks,
|
||||
handles,
|
||||
default_unit,
|
||||
default_path,
|
||||
),
|
||||
);
|
||||
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
|
||||
}
|
||||
|
||||
cmd if cmd == "-WBLOCK"
|
||||
|| cmd == "-WB"
|
||||
|| cmd.starts_with("-WBLOCK ")
|
||||
|| cmd.starts_with("-WB ")
|
||||
|| cmd.starts_with("WBLOCK ")
|
||||
|| cmd.starts_with("WB ") =>
|
||||
{
|
||||
let rest = if let Some(r) = cmd.strip_prefix("-WBLOCK") {
|
||||
r.trim()
|
||||
} else if let Some(r) = cmd.strip_prefix("-WB") {
|
||||
r.trim()
|
||||
} else if let Some(r) = cmd.strip_prefix("WBLOCK") {
|
||||
r.trim()
|
||||
} else if let Some(r) = cmd.strip_prefix("WB") {
|
||||
r.trim()
|
||||
} else {
|
||||
""
|
||||
};
|
||||
if rest.is_empty() {
|
||||
// No argument: use selected entities (*) if any, else ask.
|
||||
let sel: Vec<_> = self.tabs[i].scene.selected.iter().copied().collect();
|
||||
if sel.is_empty() {
|
||||
@@ -731,7 +813,7 @@ impl OpenCADStudio {
|
||||
return Some(Task::done(Message::WblockSave("*".to_string())));
|
||||
}
|
||||
} else {
|
||||
return Some(Task::done(Message::WblockSave(arg.to_string())));
|
||||
return Some(Task::done(Message::WblockSave(rest.to_string())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1417,11 +1499,16 @@ impl OpenCADStudio {
|
||||
if after.is_empty() {
|
||||
return Some(Task::done(Message::XAttachPick));
|
||||
}
|
||||
let cmd = crate::modules::insert::xattach::XAttachCommand::with_path(
|
||||
after.to_string(),
|
||||
self.start_xref_attach(
|
||||
i,
|
||||
crate::modules::insert::xattach::XrefAttachRequest {
|
||||
path: after.trim_matches('"').to_string(),
|
||||
overlay: false,
|
||||
path_type: crate::io::xref_model::Pathtype::Full,
|
||||
},
|
||||
crate::modules::insert::xattach::XrefPlacement::on_screen(),
|
||||
"-XREF",
|
||||
);
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
} else {
|
||||
self.command_line.push_error(crate::tf!(
|
||||
"XREF: unknown option '{}'. Options: ? Reload Unload Detach Path Pathtype Bind Overlay Attach",
|
||||
@@ -1648,11 +1735,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn xref_dash_alias_lists_like_xref() {
|
||||
// `-XREF` is accepted as an alias for industry muscle memory; bare
|
||||
// `XREF` keeps the legacy list output for compatibility.
|
||||
// `-XREF` asks on the command line: `?` then Enter prints the
|
||||
// reference table; `XREF ?` keeps the legacy list output for scripts.
|
||||
let mut app = fresh_app();
|
||||
let out = run_capture(&mut app, "-XREF ?");
|
||||
assert!(out.contains("No external references") || out.contains("External references"));
|
||||
assert!(out.contains("Total Xref(s): 0"), "got: {out:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2065,9 +2152,15 @@ mod tests {
|
||||
#[test]
|
||||
fn xref_attach_with_file_starts_placement() {
|
||||
// F10: `XREF Attach <file>` threads the file into the XATTACH
|
||||
// placement command (same flow as the picker result).
|
||||
// placement command (same flow as the picker result). A missing file
|
||||
// is rejected up front, so the reference has to exist.
|
||||
let dir = std::env::temp_dir().join(format!("ocs_xref_attach_{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let file = dir.join("plan.dwg");
|
||||
std::fs::write(&file, b"fake").unwrap();
|
||||
let mut app = fresh_app();
|
||||
let out = run_capture(&mut app, "XREF Attach C:/refs/plan.dwg");
|
||||
let out = run_capture(&mut app, &format!("XREF Attach {}", file.to_string_lossy()));
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
assert!(
|
||||
!out.contains("ships with reference operations"),
|
||||
"dead-end text must be gone, got: {out:?}"
|
||||
|
||||
@@ -5,6 +5,7 @@ use iced::Task;
|
||||
use std::path::PathBuf;
|
||||
|
||||
mod blocks;
|
||||
mod xref_attach;
|
||||
mod dim;
|
||||
pub(crate) mod display;
|
||||
mod draw;
|
||||
@@ -249,6 +250,9 @@ impl OpenCADStudio {
|
||||
if let Some(t) = self.dispatch_layers(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
if let Some(t) = self.dispatch_xref_attach(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
if let Some(t) = self.dispatch_blocks(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
@@ -663,6 +667,7 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"3DORBIT",
|
||||
"3O",
|
||||
"ABOUT",
|
||||
"ATTACH",
|
||||
"ATTDISP",
|
||||
"ATTEXT",
|
||||
"BACKGROUND",
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
//! Attaching drawing references: ATTACH / XATTACH (file picker, then the
|
||||
//! Attach External Reference dialog), the -XREF command line, the `?` list,
|
||||
//! and the commit that creates the definition and its INSERT as one undo step.
|
||||
|
||||
use super::*;
|
||||
use crate::io::xref_model::Pathtype;
|
||||
use crate::modules::insert::xattach::{
|
||||
existing_reference, path_to_block_name, prepare_xref_definition, stored_path,
|
||||
XAttachCommand, XrefAttachRequest, XrefPlacement,
|
||||
};
|
||||
use crate::modules::insert::xref_cmd::display_path;
|
||||
use crate::ui::window::xref_attach::{XrefAttachMsg, XrefAttachState};
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// Commands owned here; `None` defers to the other families.
|
||||
pub(super) fn dispatch_xref_attach(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||
let upper = cmd.to_ascii_uppercase();
|
||||
let (verb, rest) = match cmd.split_once(char::is_whitespace) {
|
||||
Some((verb, rest)) => (verb.to_ascii_uppercase(), rest.trim()),
|
||||
None => (upper.clone(), ""),
|
||||
};
|
||||
match (verb.as_str(), rest.is_empty()) {
|
||||
("ATTACH", true) => Some(Task::done(Message::AttachPick)),
|
||||
("XATTACH", true) => Some(Task::done(Message::XAttachPick)),
|
||||
// A file named on the command line skips the picker.
|
||||
// Handled at once, so the placement it starts belongs to this
|
||||
// command line (automation included).
|
||||
("ATTACH", false) => Some(self.update(Message::AttachPickResult(Ok(
|
||||
std::path::PathBuf::from(rest.trim_matches('"')),
|
||||
)))),
|
||||
("XATTACH", false) => {
|
||||
self.open_xref_attach_dialog(std::path::PathBuf::from(rest.trim_matches('"')));
|
||||
Some(Task::none())
|
||||
}
|
||||
// XREF opens the External References palette; -XREF asks on the
|
||||
// command line. Both keep their argument forms for scripts.
|
||||
("XREF", true) | ("XREFSHOW", true) => {
|
||||
if !self.show_external_references {
|
||||
return self.dispatch_blocks("EXTERNALREFERENCES", i);
|
||||
}
|
||||
self.refresh_xref_manager();
|
||||
Some(self.finish_dispatch(cmd))
|
||||
}
|
||||
("-XREF", true) => {
|
||||
let saved: Vec<(String, String)> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.block_records
|
||||
.iter()
|
||||
.filter(|br| br.flags.is_xref || br.flags.is_xref_overlay)
|
||||
.map(|br| (br.name.clone(), br.xref_path.clone()))
|
||||
.collect();
|
||||
let command = crate::modules::insert::xref_cmd::XrefCommand::new(saved);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
Some(Task::none())
|
||||
}
|
||||
("XREFLIST", _) => {
|
||||
self.list_xrefs(i, if rest.is_empty() { "*" } else { rest });
|
||||
Some(self.finish_dispatch(cmd))
|
||||
}
|
||||
("XREFATTACHFILE", false) | ("XREFOVERLAYFILE", false) => {
|
||||
let request = XrefAttachRequest {
|
||||
path: rest.trim_matches('"').to_string(),
|
||||
overlay: verb == "XREFOVERLAYFILE",
|
||||
// The reference default (REFPATHTYPE 1): relative once the
|
||||
// drawing is saved, the full path before that.
|
||||
path_type: Pathtype::Relative,
|
||||
};
|
||||
self.start_xref_attach(i, request, XrefPlacement::on_screen(), "-XREF");
|
||||
Some(Task::none())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// `-XREF ?` — the reference table as the reference application prints it.
|
||||
fn list_xrefs(&mut self, i: usize, pattern: &str) {
|
||||
let rows: Vec<(String, &'static str, String)> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.block_records
|
||||
.iter()
|
||||
.filter(|br| br.flags.is_xref || br.flags.is_xref_overlay)
|
||||
.filter(|br| {
|
||||
pattern == "*" || crate::io::xref_model::wildcard_match(&br.name, pattern)
|
||||
})
|
||||
.map(|br| {
|
||||
let kind = if br.flags.is_xref_overlay { "Overlay" } else { "Attach" };
|
||||
(br.name.clone(), kind, display_path(&br.xref_path))
|
||||
})
|
||||
.collect();
|
||||
let out = &mut self.command_line;
|
||||
out.push_output("");
|
||||
out.push_output(&format!(
|
||||
"{:<34}{:<13}{}",
|
||||
"Xref name",
|
||||
"Xref Type",
|
||||
"Path"
|
||||
));
|
||||
out.push_output(&format!("{:<34}{:<13}{}", "----------------------", "---------", "----------"));
|
||||
out.push_output("");
|
||||
for (name, kind, path) in &rows {
|
||||
out.push_output(&format!("{:<34}{:<13}{}", format!("\"{name}\""), *kind, path));
|
||||
}
|
||||
out.push_output("");
|
||||
out.push_output(&format!("Total Xref(s): {}", rows.len()));
|
||||
}
|
||||
|
||||
/// Checks the file, reports it the way the reference does, then asks
|
||||
/// for whatever is left on-screen (or commits straight away).
|
||||
pub(in crate::app) fn start_xref_attach(
|
||||
&mut self,
|
||||
i: usize,
|
||||
request: XrefAttachRequest,
|
||||
placement: XrefPlacement,
|
||||
label: &'static str,
|
||||
) {
|
||||
let name = path_to_block_name(&request.path);
|
||||
let full = crate::modules::insert::xattach::resolve_xref_store_path(&request.path, None);
|
||||
let host = self.tabs[i].current_path.clone();
|
||||
if let Some(host) = host.as_deref() {
|
||||
if crate::modules::insert::xattach::is_self_attach(host, &full, host.parent()) {
|
||||
self.command_line.push_error(
|
||||
"Error: Possible circular reference to current drawing. *Invalid*",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let existing = existing_reference(&self.tabs[i].scene, &name);
|
||||
if let Some(existing) = existing.as_ref() {
|
||||
self.command_line
|
||||
.push_output(&format!("Xref \"{}\" has already been defined.", existing));
|
||||
self.command_line
|
||||
.push_output("Using existing definition.");
|
||||
} else {
|
||||
let verb = if request.overlay {
|
||||
format!("Overlay Xref \"{}\": {}", name, display_path(&full))
|
||||
} else {
|
||||
format!("Attach Xref \"{}\": {}", name, display_path(&full))
|
||||
};
|
||||
self.command_line.push_output(&verb);
|
||||
if !std::path::Path::new(&full).is_file() {
|
||||
let file = std::path::Path::new(&full.replace('\\', "/"))
|
||||
.file_name()
|
||||
.map(|f| f.to_string_lossy().into_owned())
|
||||
.unwrap_or(full.clone());
|
||||
self.command_line
|
||||
.push_output(&format!("\"{}\" cannot be found.", file));
|
||||
self.command_line.push_error("*Invalid*");
|
||||
return;
|
||||
}
|
||||
self.command_line
|
||||
.push_output(&format!("\"{}\" loaded.", name));
|
||||
}
|
||||
let command = XAttachCommand::new(request.clone(), placement, label);
|
||||
if let Some(entity) = command.immediate() {
|
||||
self.commit_xref_attach(i, request, entity);
|
||||
return;
|
||||
}
|
||||
let (wires, source_units) = self.xref_preview(i, existing.as_deref(), &full);
|
||||
let host_units = self.tabs[i].scene.document.header.insertion_units;
|
||||
let unit = crate::app::properties::insert_unit_scale(host_units, source_units).unwrap_or(1.0);
|
||||
let command = command.with_preview(wires, unit);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
|
||||
/// The reference's geometry for the placement ghost, in its own units:
|
||||
/// an existing definition's content, or the file's model space.
|
||||
fn xref_preview(
|
||||
&self,
|
||||
i: usize,
|
||||
existing: Option<&str>,
|
||||
path: &str,
|
||||
) -> (Vec<crate::scene::model::wire_model::WireModel>, i16) {
|
||||
// ponytail: a huge reference skips the ghost; cap by entity count.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const MAX_PREVIEW_ENTITIES: usize = 20_000;
|
||||
let scene = &self.tabs[i].scene;
|
||||
if let Some(name) = existing {
|
||||
let units = scene
|
||||
.document
|
||||
.block_records
|
||||
.get(name)
|
||||
.map(|br| br.units)
|
||||
.unwrap_or(0);
|
||||
return (scene.block_preview_wires(name), units);
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Ok(source) = crate::io::load_file(std::path::Path::new(path)) {
|
||||
let owner = source.header.model_space_block_handle;
|
||||
let entities: Vec<acadrust::EntityType> = source
|
||||
.entities()
|
||||
.filter(|e| e.common().owner_handle == owner)
|
||||
.filter(|e| {
|
||||
!matches!(
|
||||
e,
|
||||
acadrust::EntityType::Block(_) | acadrust::EntityType::BlockEnd(_)
|
||||
)
|
||||
})
|
||||
.take(MAX_PREVIEW_ENTITIES + 1)
|
||||
.cloned()
|
||||
.collect();
|
||||
if entities.len() <= MAX_PREVIEW_ENTITIES {
|
||||
return (
|
||||
scene.wires_for_entities(&entities),
|
||||
source.header.insertion_units,
|
||||
);
|
||||
}
|
||||
}
|
||||
let _ = path;
|
||||
(Vec::new(), 0)
|
||||
}
|
||||
|
||||
/// Creates (or reuses) the definition and commits its INSERT — one undo
|
||||
/// step for both, so undo leaves no definition behind.
|
||||
pub(in crate::app) fn commit_xref_attach(
|
||||
&mut self,
|
||||
i: usize,
|
||||
request: XrefAttachRequest,
|
||||
mut entity: acadrust::EntityType,
|
||||
) {
|
||||
let pending = self.begin_undo(i, "XATTACH", 1, false);
|
||||
let host = self.tabs[i].current_path.clone();
|
||||
let name = prepare_xref_definition(&mut self.tabs[i].scene, &request, host.as_deref());
|
||||
if host.is_none() && request.path_type == Pathtype::Relative {
|
||||
self.tabs[i].xref_relative_on_save.insert(name.clone());
|
||||
}
|
||||
if let acadrust::EntityType::Insert(insert) = &mut entity {
|
||||
insert.block_name = name;
|
||||
}
|
||||
// Resolving merged the reference's layers and linetypes — mirror
|
||||
// them into the Layers panel and ribbon dropdowns now (#407).
|
||||
self.refresh_layer_panel();
|
||||
let _ = self.commit_entity_handle(entity);
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.restore_pre_cmd_tangent();
|
||||
if let Some(pending) = pending {
|
||||
self.commit_undo_delta(i, pending);
|
||||
}
|
||||
self.refresh_xref_manager();
|
||||
}
|
||||
|
||||
/// Opens the Attach External Reference dialog for `path`.
|
||||
pub(in crate::app) fn open_xref_attach_dialog(&mut self, path: std::path::PathBuf) {
|
||||
let i = self.active_tab;
|
||||
let existing: Vec<(String, String)> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.block_records
|
||||
.iter()
|
||||
.filter(|br| br.flags.is_xref || br.flags.is_xref_overlay)
|
||||
.map(|br| (br.name.clone(), br.xref_path.clone()))
|
||||
.collect();
|
||||
let mut state = XrefAttachState::new(existing);
|
||||
self.load_xref_attach_file(&mut state, &path.to_string_lossy());
|
||||
self.xref_attach = Some(state);
|
||||
self.active_modal = Some(crate::app::ModalKind::XrefAttach);
|
||||
}
|
||||
|
||||
/// Fills the dialog from the chosen file: name, preview, block unit and
|
||||
/// the paths shown under Details.
|
||||
fn load_xref_attach_file(&self, state: &mut XrefAttachState, path: &str) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let i = self.active_tab;
|
||||
state.path = path.to_string();
|
||||
state.name = path_to_block_name(path);
|
||||
state.found_in = display_path(path);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
state.preview = crate::io::thumbnail::read_handle(std::path::Path::new(path));
|
||||
let host_units = self.tabs[i].scene.document.header.insertion_units;
|
||||
let source_units = crate::io::load_file(std::path::Path::new(path))
|
||||
.map(|doc| doc.header.insertion_units)
|
||||
.unwrap_or(0);
|
||||
state.unit_label = crate::t!(crate::app::properties::insunits_name(source_units))
|
||||
.into_owned();
|
||||
let factor =
|
||||
crate::app::properties::insert_unit_scale(host_units, source_units).unwrap_or(1.0);
|
||||
state.unit_factor = crate::app::properties::format_unit_factor(factor);
|
||||
}
|
||||
self.refresh_xref_attach_saved_path(state);
|
||||
}
|
||||
|
||||
fn refresh_xref_attach_saved_path(&self, state: &mut XrefAttachState) {
|
||||
let host = self.tabs[self.active_tab].current_path.clone();
|
||||
let request = XrefAttachRequest {
|
||||
path: state.path.clone(),
|
||||
overlay: state.overlay,
|
||||
path_type: state.path_type.0,
|
||||
};
|
||||
state.saved_path = display_path(&stored_path(&request, host.as_deref()));
|
||||
}
|
||||
|
||||
/// Messages of the ATTACH picker and the dialog.
|
||||
pub(in crate::app) fn update_xref_attach(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::AttachPick => Task::perform(
|
||||
async {
|
||||
let handle = crate::sys::file_dialog()
|
||||
.set_title(crate::t!("Select Reference File").as_ref())
|
||||
.add_filter(
|
||||
crate::t!("All Reference Files").as_ref(),
|
||||
&[
|
||||
"dwg", "dxf", "pdf", "png", "jpg", "jpeg", "bmp", "tif", "tiff",
|
||||
],
|
||||
)
|
||||
.add_filter(crate::t!("Drawing").as_ref(), &["dwg", "dxf"])
|
||||
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
|
||||
.add_filter(
|
||||
crate::t!("Images").as_ref(),
|
||||
&["png", "jpg", "jpeg", "bmp", "tif", "tiff"],
|
||||
)
|
||||
.pick_file()
|
||||
.await;
|
||||
match handle {
|
||||
Some(h) => Ok(crate::sys::handle_path(&h)),
|
||||
None => Err("Cancelled".to_string()),
|
||||
}
|
||||
},
|
||||
Message::AttachPickResult,
|
||||
),
|
||||
Message::AttachPickResult(Ok(path)) => {
|
||||
let ext = path
|
||||
.extension()
|
||||
.map(|e| e.to_string_lossy().to_ascii_lowercase())
|
||||
.unwrap_or_default();
|
||||
match ext.as_str() {
|
||||
"pdf" => match std::fs::read(&path) {
|
||||
Ok(bytes) => self.update(Message::PdfAttachPickResult(Ok((
|
||||
path,
|
||||
std::sync::Arc::new(bytes),
|
||||
)))),
|
||||
Err(e) => self.update(Message::PdfAttachPickResult(Err(e.to_string()))),
|
||||
},
|
||||
"png" | "jpg" | "jpeg" | "bmp" | "tif" | "tiff" => {
|
||||
let result = image::image_dimensions(&path)
|
||||
.map(|(w, h)| (path.clone(), w, h))
|
||||
.map_err(|e| e.to_string());
|
||||
self.update(Message::ImagePickResult(result))
|
||||
}
|
||||
_ => {
|
||||
self.open_xref_attach_dialog(path);
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::AttachPickResult(Err(e)) => {
|
||||
if e != "Cancelled" {
|
||||
self.command_line
|
||||
.push_error(format!("ATTACH: {e}").as_ref());
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::XrefAttachBrowseResult(Ok(path)) => {
|
||||
if let Some(mut state) = self.xref_attach.take() {
|
||||
self.load_xref_attach_file(&mut state, &path.to_string_lossy());
|
||||
self.xref_attach = Some(state);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::XrefAttachBrowseResult(Err(_)) => Task::none(),
|
||||
Message::XrefAttach(edit) => self.edit_xref_attach(edit),
|
||||
_ => Task::none(),
|
||||
}
|
||||
}
|
||||
|
||||
fn edit_xref_attach(&mut self, edit: XrefAttachMsg) -> Task<Message> {
|
||||
let Some(mut state) = self.xref_attach.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
let mut task = Task::none();
|
||||
match edit {
|
||||
XrefAttachMsg::Name(name) => {
|
||||
if let Some((_, saved)) = state
|
||||
.existing
|
||||
.iter()
|
||||
.find(|(n, _)| n.eq_ignore_ascii_case(&name))
|
||||
.cloned()
|
||||
{
|
||||
let host = self.tabs[self.active_tab].current_path.clone();
|
||||
let base = host.as_deref().and_then(|h| h.parent());
|
||||
let full = crate::modules::insert::xattach::resolve_xref_store_path(&saved, base);
|
||||
self.load_xref_attach_file(&mut state, &full);
|
||||
}
|
||||
}
|
||||
XrefAttachMsg::Browse => {
|
||||
task = Task::perform(
|
||||
async {
|
||||
let handle = crate::sys::file_dialog()
|
||||
.set_title(crate::t!("Select Reference File").as_ref())
|
||||
.add_filter(crate::t!("Drawing").as_ref(), &["dwg", "dxf"])
|
||||
.pick_file()
|
||||
.await;
|
||||
match handle {
|
||||
Some(h) => Ok(crate::sys::handle_path(&h)),
|
||||
None => Err("Cancelled".to_string()),
|
||||
}
|
||||
},
|
||||
Message::XrefAttachBrowseResult,
|
||||
);
|
||||
}
|
||||
XrefAttachMsg::Overlay(overlay) => state.overlay = overlay,
|
||||
XrefAttachMsg::ScaleOnScreen(on) => state.scale_on_screen = on,
|
||||
XrefAttachMsg::Scale(axis, value) => {
|
||||
if axis == 0 && state.uniform {
|
||||
state.scale = [value.clone(), value.clone(), value];
|
||||
} else if let Some(slot) = state.scale.get_mut(axis) {
|
||||
*slot = value;
|
||||
}
|
||||
}
|
||||
XrefAttachMsg::Uniform(on) => {
|
||||
state.uniform = on;
|
||||
if on {
|
||||
let x = state.scale[0].clone();
|
||||
state.scale = [x.clone(), x.clone(), x];
|
||||
}
|
||||
}
|
||||
XrefAttachMsg::InsertOnScreen(on) => state.insert_on_screen = on,
|
||||
XrefAttachMsg::Insert(axis, value) => {
|
||||
if let Some(slot) = state.insert.get_mut(axis) {
|
||||
*slot = value;
|
||||
}
|
||||
}
|
||||
XrefAttachMsg::PathType(choice) => {
|
||||
state.path_type = choice;
|
||||
self.refresh_xref_attach_saved_path(&mut state);
|
||||
}
|
||||
XrefAttachMsg::RotationOnScreen(on) => state.rotation_on_screen = on,
|
||||
XrefAttachMsg::Rotation(value) => state.rotation = value,
|
||||
XrefAttachMsg::Details(on) => state.details = on,
|
||||
XrefAttachMsg::Help => {
|
||||
self.command_line.push_info(
|
||||
crate::t!("Inserts references to external files such as other drawings, raster images, and underlays.")
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
XrefAttachMsg::Apply => {
|
||||
let placement = XrefPlacement {
|
||||
insert: (!state.insert_on_screen)
|
||||
.then(|| state.insert_point())
|
||||
.flatten(),
|
||||
scale: (!state.scale_on_screen).then(|| state.scale_values()).flatten(),
|
||||
rotation: (!state.rotation_on_screen)
|
||||
.then(|| crate::entities::common::parse_typed_angle(&state.rotation))
|
||||
.flatten()
|
||||
.map(f64::to_degrees),
|
||||
};
|
||||
let invalid = (!state.insert_on_screen && placement.insert.is_none())
|
||||
|| (!state.scale_on_screen
|
||||
&& placement
|
||||
.scale
|
||||
.is_none_or(|s| s.iter().any(|v| *v == 0.0)))
|
||||
|| (!state.rotation_on_screen && placement.rotation.is_none());
|
||||
if invalid || state.path.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("Invalid input.").as_ref());
|
||||
self.xref_attach = Some(state);
|
||||
return Task::none();
|
||||
}
|
||||
let request = XrefAttachRequest {
|
||||
path: state.path.clone(),
|
||||
overlay: state.overlay,
|
||||
path_type: state.path_type.0,
|
||||
};
|
||||
self.active_modal = None;
|
||||
let i = self.active_tab;
|
||||
self.start_xref_attach(i, request, placement, "XATTACH");
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
self.xref_attach = Some(state);
|
||||
task
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user