feat(attedit): attribute editor dialog for blocks

Double-clicking a block reference that has attributes, or running ATTEDIT
on one, now opens an editor dialog listing every attribute (tag + editable
value) with OK / Cancel. OK writes the values back to the block, with undo
and a repaint; Cancel discards. A block with no attributes still enters
in-place block edit (REFEDIT) on double-click as before. (#192)

ATTEDIT opens the dialog directly when a suitable block is already
selected; otherwise it runs the pick command and the dialog opens once a
block is chosen. This replaces the earlier command-line, per-attribute
prompt flow, which is removed along with its __ATTEDIT__ sentinel path and
the now-unused attedit_set_attrs trait hook; the ATTEDIT command is reduced
to a plain block picker.

The dialog is a tab-scoped in-canvas modal (Plan B): its working copy holds
a document-local handle, so closing that tab or switching away dismisses the
editor rather than risk applying edits to another tab's document.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-07-01 08:05:25 +03:00
commit 86a59d6c7e
12 changed files with 346 additions and 192 deletions

View file

@ -172,35 +172,17 @@ impl OpenCADStudio {
let i = self.active_tab;
match result {
CmdResult::NeedPoint => {
// If ATTEDIT just completed entity pick, inject attribute data.
// ATTEDIT finished its entity pick: hand the chosen block off to
// the attribute editor dialog and end the command (open_attribute
// _editor reports "no attributes" / "select a block" as needed).
let attedit_handle = self.tabs[i]
.active_cmd
.as_ref()
.and_then(|c| c.attedit_pending_handle());
if let Some(ins_handle) = attedit_handle {
if let Some(acadrust::EntityType::Insert(ins)) =
self.tabs[i].scene.document.get_entity(ins_handle)
{
let attrs: Vec<(String, String)> = ins
.attributes
.iter()
.map(|a| (a.tag.clone(), a.get_value().to_string()))
.collect();
if attrs.is_empty() {
self.command_line
.push_error("ATTEDIT This INSERT has no attributes.");
self.tabs[i].active_cmd = None;
return Task::none();
}
if let Some(cmd) = &mut self.tabs[i].active_cmd {
cmd.attedit_set_attrs(attrs);
}
} else {
self.command_line
.push_error("ATTEDIT Please select an INSERT entity with attributes.");
self.tabs[i].active_cmd = None;
return Task::none();
}
self.tabs[i].active_cmd = None;
self.open_attribute_editor(ins_handle);
return Task::none();
}
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
if let Some(p) = prompt {
@ -458,28 +440,6 @@ impl OpenCADStudio {
self.refresh_properties();
}
CmdResult::ReplaceEntity(handle, new_entities) => {
// Detect ATTEDIT sentinel.
if new_entities.len() == 1 {
if let acadrust::EntityType::XLine(ref xl) = new_entities[0] {
let layer = xl.common.layer.clone();
if let Some(encoded) = layer.strip_prefix("__ATTEDIT__") {
let label = self.history_label_from_active_cmd(i, "ATTEDIT");
self.push_undo_snapshot(i, label);
crate::modules::draw::modify::attedit::apply_attedit(
&mut self.tabs[i].scene.document,
handle,
encoded,
);
self.tabs[i].dirty = true;
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.command_line
.push_output("ATTEDIT Attribute values updated.");
return Task::none();
}
}
}
// Detect SPLINEDIT sentinel: a single XLine with a magic layer name.
if new_entities.len() == 1 {
if let acadrust::EntityType::XLine(ref xl) = new_entities[0] {