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 {
handle: *h,
original_tag: def.tag.clone(),
live_tag: def.tag.clone(),
def: def.clone(),
}),
_ => None,
@@ -252,12 +253,9 @@ impl OpenCADStudio {
return Task::none();
}
AttManMsg::Cancel => {
let steps = state.live_steps;
self.attman_withdraw_live();
self.attman = None;
self.close_active_modal();
if steps > 0 {
self.undo_steps(steps);
}
return Task::none();
}
_ => {
@@ -374,10 +372,26 @@ impl OpenCADStudio {
}
}
/// Write the working copy to the drawing. `finish` closes the undo step
/// (Apply / OK / Sync); otherwise the step stays open for the next live
/// edit. `sync` rebuilds the references' attributes even when "apply to
/// existing references" is off. Returns false when a field is invalid.
/// Take back the changes written live since the last Apply, quietly.
fn attman_withdraw_live(&mut self) {
let Some(state) = self.attman.as_mut() else {
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 {
let Some(state) = self.attman.as_mut() else {
return false;
@@ -396,48 +410,51 @@ impl OpenCADStudio {
return false;
}
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 {
return false;
};
let rows: Vec<(Handle, String, AttributeDefinition)> = state
.rows
.iter()
.map(|r| (r.handle, r.original_tag.clone(), r.def.clone()))
.map(|r| (r.handle, r.live_tag.clone(), r.def.clone()))
.collect();
let removed = state.removed.clone();
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 {
// One undo step; a live step something else closed starts anew.
if !open {
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 {
return false;
};
if finish {
if state.live_steps > 0 || write {
self.finish_pending_history(i);
}
if let Some(state) = self.attman.as_mut() {
state.live_steps = 0;
state.dirty = false;
state.removed.clear();
for row in &mut state.rows {
row.original_tag = row.def.tag.clone();
}
state.dirty = false;
state.removed.clear();
for row in &mut state.rows {
row.original_tag = row.def.tag.clone();
row.live_tag = row.def.tag.clone();
}
} else if write {
if !open {
state.live_steps += 1;
}
// The references now hold the written tags.
if sync || dialog::prefs().apply_existing {
if synced {
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) {
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;
self.finish_pending_history(i);
let available = self.tabs[i].history.undo_stack.len();
@@ -1353,8 +1359,10 @@ impl OpenCADStudio {
native_graph_changed,
&changes,
);
self.command_line
.push_output(crate::tf!("Undo: {last_label}").as_ref());
if report {
self.command_line
.push_output(crate::tf!("Undo: {last_label}").as_ref());
}
}
pub(super) fn redo_steps(&mut self, steps: usize) {
+3 -2
View File
@@ -179,9 +179,10 @@ pub struct AttManBlock {
pub struct AttManRow {
/// The definition object in the block.
pub handle: Handle,
/// The tag the definition had when the block was loaded, for keeping the
/// references' values when it is renamed.
/// The tag the definition had when the block was loaded or applied.
pub original_tag: String,
/// The tag the references hold now (changed by live writes).
pub live_tag: String,
/// The working copy.
pub def: AttributeDefinition,
}