feat(plugin): harden V4 host integration
Preserve legacy wire indices and route concurrent V4 responses and background requests by correlation and stable tab ID.
This commit is contained in:
commit
f247d15f6e
110 changed files with 32160 additions and 1255 deletions
|
|
@ -1110,39 +1110,14 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
.get(idx)
|
||||
.map(|l| l.name.clone())
|
||||
.unwrap_or_default();
|
||||
if !new_name.is_empty()
|
||||
&& new_name != old_name
|
||||
&& !self.tabs[i].scene.document.layers.contains(&new_name)
|
||||
{
|
||||
if !new_name.is_empty() && new_name != old_name {
|
||||
self.push_undo_snapshot(i, "LAYER RENAME");
|
||||
// Keep the whole record (handle, color, linetype,
|
||||
// lineweight, flags) and only change the name, so the
|
||||
// renamed layer still has a valid handle and survives a
|
||||
// DWG save (issue #67).
|
||||
if let Some(mut nl) =
|
||||
self.tabs[i].scene.document.layers.get(&old_name).cloned()
|
||||
{
|
||||
nl.name = new_name.clone();
|
||||
if !nl.handle.is_valid() {
|
||||
nl.handle = self.tabs[i].scene.document.allocate_handle();
|
||||
}
|
||||
let _ = self.tabs[i].scene.document.layers.add(nl);
|
||||
if !self.tabs[i].rename_layer(&old_name, &new_name) {
|
||||
self.discard_last_undo_entry(i);
|
||||
}
|
||||
self.tabs[i].scene.document.layers.remove(&old_name);
|
||||
for e in self.tabs[i].scene.document.entities_mut() {
|
||||
if e.as_entity().layer() == old_name {
|
||||
e.as_entity_mut().set_layer(new_name.clone());
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
let doc_layers = self.tabs[i].scene.document.layers.clone();
|
||||
let vp_info = self.tabs[i].scene.viewport_list();
|
||||
self.tabs[i]
|
||||
.layers
|
||||
.sync_with_viewports(&doc_layers, vp_info);
|
||||
self.tabs[i].layers.edit_buf.clear();
|
||||
self.sync_ribbon_layers();
|
||||
self.refresh_layer_panel();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -1991,7 +1966,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
// selected inserts to the picked definition. A stale
|
||||
// typed value in the text buffer would mask the pick,
|
||||
// so drop it; the pick also closes the list.
|
||||
self.tabs[i].properties.edit_buf.remove("block");
|
||||
self.tabs[i]
|
||||
.properties
|
||||
.edit_buf
|
||||
.remove(&crate::ui::properties::FieldKey::Geom("block"));
|
||||
self.tabs[i].properties.edit_choice_open = false;
|
||||
let canon = self.tabs[i]
|
||||
.scene
|
||||
|
|
@ -2127,9 +2105,14 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
|
||||
pub(super) fn on_prop_geom_commit(&mut self, field: &'static str) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].properties.active_field = None;
|
||||
let handles = self.property_target_handles(i);
|
||||
if !handles.is_empty() {
|
||||
if let Some(raw_val) = self.tabs[i].properties.edit_buf.remove(field) {
|
||||
if let Some(raw_val) = self.tabs[i]
|
||||
.properties
|
||||
.edit_buf
|
||||
.remove(&crate::ui::properties::FieldKey::Geom(field))
|
||||
{
|
||||
// Block names are free-form text — a name like "10-5"
|
||||
// must not be arithmetic-evaluated.
|
||||
let val = if field == "block" {
|
||||
|
|
@ -2355,6 +2338,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
/// it is not run through the expression evaluator like numeric fields).
|
||||
pub(super) fn on_prop_attr_commit(&mut self, tag: String) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].properties.active_field = None;
|
||||
let key = crate::ui::properties::attr_edit_key(&tag);
|
||||
let handles = self.property_target_handles(i);
|
||||
if handles.is_empty() {
|
||||
|
|
@ -2609,3 +2593,180 @@ fn block_attr_prompts(
|
|||
}
|
||||
map
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod layer_rename_tests {
|
||||
use crate::app::OpenCADStudio;
|
||||
use acadrust::entities::Line;
|
||||
use acadrust::{EntityType, Handle};
|
||||
|
||||
fn app_with_editing_layer() -> OpenCADStudio {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
let _ = app.on_layer_new();
|
||||
app
|
||||
}
|
||||
|
||||
fn rename_layer(app: &mut OpenCADStudio, old_name: &str, new_name: &str) {
|
||||
let i = app.active_tab;
|
||||
let idx = app.tabs[i]
|
||||
.layers
|
||||
.layers
|
||||
.iter()
|
||||
.position(|layer| layer.name == old_name)
|
||||
.expect("layer exists in panel");
|
||||
app.tabs[i].layers.editing = Some(idx);
|
||||
app.tabs[i].layers.edit_buf = new_name.to_string();
|
||||
let _ = app.on_layer_rename_commit();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_rename_allows_ascii_case_only_change() {
|
||||
let mut app = app_with_editing_layer();
|
||||
let i = app.active_tab;
|
||||
let old_name = app.tabs[i].layers.edit_buf.clone();
|
||||
rename_layer(&mut app, &old_name, "TEST");
|
||||
let handle = app.tabs[i].scene.document.layers.get("TEST").unwrap().handle;
|
||||
|
||||
rename_layer(&mut app, "TEST", "test");
|
||||
|
||||
let layer = app.tabs[i].scene.document.layers.get("test").unwrap();
|
||||
assert_eq!(layer.name, "test");
|
||||
assert_eq!(layer.handle, handle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_rename_allows_unicode_case_only_change() {
|
||||
let mut app = app_with_editing_layer();
|
||||
let i = app.active_tab;
|
||||
let old_name = app.tabs[i].layers.edit_buf.clone();
|
||||
rename_layer(&mut app, &old_name, "é");
|
||||
|
||||
rename_layer(&mut app, "é", "É");
|
||||
|
||||
assert_eq!(
|
||||
app.tabs[i].scene.document.layers.get("É").unwrap().name,
|
||||
"É"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_layer_rename_updates_name_and_allocated_handle() {
|
||||
let mut app = app_with_editing_layer();
|
||||
let i = app.active_tab;
|
||||
let idx = app.tabs[i].layers.editing.expect("new layer is being edited");
|
||||
let old_name = app.tabs[i].layers.layers[idx].name.clone();
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.layers
|
||||
.get_mut(&old_name)
|
||||
.unwrap()
|
||||
.handle = Handle::NULL;
|
||||
app.tabs[i].layers.selected = Some(idx);
|
||||
let _ = app.on_layer_set_current();
|
||||
|
||||
rename_layer(&mut app, &old_name, "Renamed");
|
||||
|
||||
let layer = app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.layers
|
||||
.get("Renamed")
|
||||
.unwrap();
|
||||
assert!(layer.handle.is_valid());
|
||||
assert_eq!(app.tabs[i].active_layer, "Renamed");
|
||||
assert_eq!(app.tabs[i].layers.current_layer, "Renamed");
|
||||
assert_eq!(app.ribbon.active_layer, "Renamed");
|
||||
assert_eq!(
|
||||
app.tabs[i].scene.document.header.current_layer_name,
|
||||
"Renamed"
|
||||
);
|
||||
assert_eq!(
|
||||
app.tabs[i].scene.document.header.current_layer_handle,
|
||||
layer.handle
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_rename_undo_redo_restores_active_layer() {
|
||||
let mut app = app_with_editing_layer();
|
||||
let i = app.active_tab;
|
||||
let idx = app.tabs[i].layers.editing.expect("new layer is being edited");
|
||||
let old_name = app.tabs[i].layers.layers[idx].name.clone();
|
||||
app.tabs[i].layers.selected = Some(idx);
|
||||
let _ = app.on_layer_set_current();
|
||||
|
||||
rename_layer(&mut app, &old_name, "Renamed");
|
||||
app.undo_active_tab();
|
||||
|
||||
assert!(app.tabs[i].scene.document.layers.contains(&old_name));
|
||||
assert_eq!(app.tabs[i].active_layer, old_name);
|
||||
assert_eq!(app.ribbon.active_layer, app.tabs[i].active_layer);
|
||||
|
||||
app.redo_active_tab();
|
||||
|
||||
assert!(app.tabs[i].scene.document.layers.contains("Renamed"));
|
||||
assert_eq!(app.tabs[i].active_layer, "Renamed");
|
||||
assert_eq!(app.ribbon.active_layer, "Renamed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_rename_keeps_independent_current_layer_mirrors() {
|
||||
let mut app = app_with_editing_layer();
|
||||
let i = app.active_tab;
|
||||
let old_name = app.tabs[i].layers.edit_buf.clone();
|
||||
rename_layer(&mut app, &old_name, "A");
|
||||
let _ = app.on_layer_new();
|
||||
let b_idx = app.tabs[i].layers.editing.expect("new layer is being edited");
|
||||
app.tabs[i].layers.selected = Some(b_idx);
|
||||
let _ = app.on_layer_set_current();
|
||||
let header_name = app.tabs[i].scene.document.header.current_layer_name.clone();
|
||||
let header_handle = app.tabs[i].scene.document.header.current_layer_handle;
|
||||
app.tabs[i].active_layer = "A".to_string();
|
||||
|
||||
rename_layer(&mut app, "A", "Renamed");
|
||||
|
||||
assert_eq!(app.tabs[i].active_layer, "Renamed");
|
||||
assert_eq!(
|
||||
app.tabs[i].scene.document.header.current_layer_name,
|
||||
header_name
|
||||
);
|
||||
assert_eq!(
|
||||
app.tabs[i].scene.document.header.current_layer_handle,
|
||||
header_handle
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_rename_updates_mixed_case_entity_references() {
|
||||
let mut app = app_with_editing_layer();
|
||||
let i = app.active_tab;
|
||||
let old_name = app.tabs[i].layers.edit_buf.clone();
|
||||
rename_layer(&mut app, &old_name, "TEST");
|
||||
let mut line = Line::new();
|
||||
line.common.layer = "test".to_string();
|
||||
let handle = app.tabs[i].scene.add_entity(EntityType::Line(line));
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.invalidate_layer_dependencies(&["TEST".to_string()]);
|
||||
|
||||
rename_layer(&mut app, "TEST", "Renamed");
|
||||
|
||||
assert_eq!(
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.unwrap()
|
||||
.common()
|
||||
.layer,
|
||||
"Renamed"
|
||||
);
|
||||
let epoch = app.tabs[i].scene.geometry_epoch;
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.invalidate_layer_dependencies(&["Renamed".to_string()]);
|
||||
assert_ne!(app.tabs[i].scene.geometry_epoch, epoch);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue