Block Attribute Manager: one undo step per Apply

Live changes are taken back quietly before Apply, OK or Sync write the
list again, so each leaves exactly one undo step even when something
closed the open live step in between; Cancel takes them back without a
command-line notice. References keep their values through renames made
live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-15 10:34:58 +03:00
1 parent 4726c775bf
commit 3bd828d0f5
3 files changed
+54 -28

No files matched your search

+41 -24
View File
@@ -111,6 +111,7 @@ impl OpenCADStudio {
Some(EntityType::AttributeDefinition(def)) => Some(AttManRow { Some(EntityType::AttributeDefinition(def)) => Some(AttManRow {
handle: *h, handle: *h,
original_tag: def.tag.clone(), original_tag: def.tag.clone(),
live_tag: def.tag.clone(),
def: def.clone(), def: def.clone(),
}), }),
_ => None, _ => None,
@@ -252,12 +253,9 @@ impl OpenCADStudio {
return Task::none(); return Task::none();
} }
AttManMsg::Cancel => { AttManMsg::Cancel => {
let steps = state.live_steps; self.attman_withdraw_live();
self.attman = None; self.attman = None;
self.close_active_modal(); self.close_active_modal();
if steps > 0 {
self.undo_steps(steps);
}
return Task::none(); return Task::none();
} }
_ => { _ => {
@@ -374,10 +372,26 @@ impl OpenCADStudio {
} }
} }
/// Write the working copy to the drawing. `finish` closes the undo step /// Take back the changes written live since the last Apply, quietly.
/// (Apply / OK / Sync); otherwise the step stays open for the next live fn attman_withdraw_live(&mut self) {
/// edit. `sync` rebuilds the references' attributes even when "apply to let Some(state) = self.attman.as_mut() else {
/// existing references" is off. Returns false when a field is invalid. return;
};
let steps = std::mem::take(&mut state.live_steps);
for row in &mut state.rows {
row.live_tag = row.original_tag.clone();
}
if steps > 0 {
self.undo_steps_reported(steps, false);
}
}
/// Write the working copy to the drawing. `finish` (Apply / OK / Sync)
/// leaves exactly one undo step: the live changes are taken back and the
/// whole list is written again in one step. Otherwise (a live edit) the
/// write joins the open live step. `sync` rebuilds the references'
/// attributes even when "apply to existing references" is off. Returns
/// false when a field is invalid.
fn attman_commit(&mut self, i: usize, finish: bool, sync: bool) -> bool { fn attman_commit(&mut self, i: usize, finish: bool, sync: bool) -> bool {
let Some(state) = self.attman.as_mut() else { let Some(state) = self.attman.as_mut() else {
return false; return false;
@@ -396,48 +410,51 @@ impl OpenCADStudio {
return false; return false;
} }
state.error = None; state.error = None;
self.attman_withdraw_live();
} }
let Some(state) = self.attman.as_mut() else {
return false;
};
let Some(name) = state.current_block().map(|b| b.name.clone()) else { let Some(name) = state.current_block().map(|b| b.name.clone()) else {
return false; return false;
}; };
let rows: Vec<(Handle, String, AttributeDefinition)> = state let rows: Vec<(Handle, String, AttributeDefinition)> = state
.rows .rows
.iter() .iter()
.map(|r| (r.handle, r.original_tag.clone(), r.def.clone())) .map(|r| (r.handle, r.live_tag.clone(), r.def.clone()))
.collect(); .collect();
let removed = state.removed.clone(); let removed = state.removed.clone();
let write = state.dirty || sync; let write = state.dirty || sync;
let open = state.live_steps > 0 && self.tabs[i].history.pending.is_some(); let synced = sync || dialog::prefs().apply_existing;
// A live step something else closed starts a new one.
let open = !finish && state.live_steps > 0 && self.tabs[i].history.pending.is_some();
if write { if write {
// One undo step; a live step something else closed starts anew.
if !open { if !open {
self.push_undo_snapshot(i, "BATTMAN"); self.push_undo_snapshot(i, "BATTMAN");
} }
self.attman_write(i, &name, &rows, &removed, sync || dialog::prefs().apply_existing); self.attman_write(i, &name, &rows, &removed, synced);
}
if finish && write {
self.finish_pending_history(i);
} }
let Some(state) = self.attman.as_mut() else { let Some(state) = self.attman.as_mut() else {
return false; return false;
}; };
if finish { if finish {
if state.live_steps > 0 || write { state.dirty = false;
self.finish_pending_history(i); state.removed.clear();
} for row in &mut state.rows {
if let Some(state) = self.attman.as_mut() { row.original_tag = row.def.tag.clone();
state.live_steps = 0; row.live_tag = row.def.tag.clone();
state.dirty = false;
state.removed.clear();
for row in &mut state.rows {
row.original_tag = row.def.tag.clone();
}
} }
} else if write { } else if write {
if !open { if !open {
state.live_steps += 1; state.live_steps += 1;
} }
// The references now hold the written tags. // The references now hold the written tags.
if sync || dialog::prefs().apply_existing { if synced {
for row in &mut state.rows { for row in &mut state.rows {
row.original_tag = row.def.tag.clone(); row.live_tag = row.def.tag.clone();
} }
} }
} }
+10 -2
View File
@@ -1287,6 +1287,12 @@ impl OpenCADStudio {
} }
pub(super) fn undo_steps(&mut self, steps: usize) { pub(super) fn undo_steps(&mut self, steps: usize) {
self.undo_steps_reported(steps, true);
}
/// [`Self::undo_steps`]; `report` false takes the steps back without
/// the command-line notice (a dialog withdrawing its own live changes).
pub(super) fn undo_steps_reported(&mut self, steps: usize, report: bool) {
let i = self.active_tab; let i = self.active_tab;
self.finish_pending_history(i); self.finish_pending_history(i);
let available = self.tabs[i].history.undo_stack.len(); let available = self.tabs[i].history.undo_stack.len();
@@ -1353,8 +1359,10 @@ impl OpenCADStudio {
native_graph_changed, native_graph_changed,
&changes, &changes,
); );
self.command_line if report {
.push_output(crate::tf!("Undo: {last_label}").as_ref()); self.command_line
.push_output(crate::tf!("Undo: {last_label}").as_ref());
}
} }
pub(super) fn redo_steps(&mut self, steps: usize) { pub(super) fn redo_steps(&mut self, steps: usize) {
+3 -2
View File
@@ -179,9 +179,10 @@ pub struct AttManBlock {
pub struct AttManRow { pub struct AttManRow {
/// The definition object in the block. /// The definition object in the block.
pub handle: Handle, pub handle: Handle,
/// The tag the definition had when the block was loaded, for keeping the /// The tag the definition had when the block was loaded or applied.
/// references' values when it is renamed.
pub original_tag: String, pub original_tag: String,
/// The tag the references hold now (changed by live writes).
pub live_tag: String,
/// The working copy. /// The working copy.
pub def: AttributeDefinition, pub def: AttributeDefinition,
} }