fix(layers): protect locked entities
Keep locked-layer objects selectable for inspection while suppressing grips, property edits, command mutations, and stale editor writes.
This commit is contained in:
parent
ca331fff48
commit
0be9c41773
25 changed files with 524 additions and 118 deletions
|
|
@ -239,6 +239,11 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
// Grip-menu value prompt — consume the typed number and
|
||||
// route it through `apply_grip_menu_value`.
|
||||
if let Some(pending) = self.grip_pending.take() {
|
||||
let i = self.active_tab;
|
||||
if self.reject_locked_edit(i, pending.handle) {
|
||||
self.cancel_active_grip_edit();
|
||||
return Task::none();
|
||||
}
|
||||
let raw = crate::app::expr_eval::eval_to_string(self.command_line.input.trim());
|
||||
self.command_line.input.clear();
|
||||
let Ok(v) = raw.parse::<f64>() else {
|
||||
|
|
@ -249,7 +254,6 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
self.grip_pending = Some(pending);
|
||||
return self.focus_cmd_input();
|
||||
};
|
||||
let i = self.active_tab;
|
||||
let interactive_lengthen = self.tabs[i]
|
||||
.active_grip
|
||||
.as_ref()
|
||||
|
|
@ -1141,6 +1145,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
let Some(popup) = self.grip_popup.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
if self.reject_locked_edit(i, popup.handle) {
|
||||
return Task::none();
|
||||
}
|
||||
self.grip_hover = None;
|
||||
let Some(item) = popup.items.get(idx).cloned() else {
|
||||
return Task::none();
|
||||
|
|
@ -1517,6 +1524,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
self.ribbon.close_dropdown();
|
||||
let handles = self.property_target_handles(i);
|
||||
if handles.is_empty() {
|
||||
if self.has_property_selection(i) {
|
||||
return Task::none();
|
||||
}
|
||||
// No selection — change the creation default. Persist
|
||||
// into the tab's header (CLAYER) so it survives a tab
|
||||
// switch and rides the next save. #21.
|
||||
|
|
@ -1559,6 +1569,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
self.ribbon.close_dropdown();
|
||||
let handles = self.property_target_handles(i);
|
||||
if handles.is_empty() {
|
||||
if self.has_property_selection(i) {
|
||||
return Task::none();
|
||||
}
|
||||
// Persist the new default into the tab's header so it
|
||||
// round-trips through tab switches and writes back on
|
||||
// save (CECOLOR). #21.
|
||||
|
|
@ -1585,6 +1598,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
self.ribbon.close_dropdown();
|
||||
let handles = self.property_target_handles(i);
|
||||
if handles.is_empty() {
|
||||
if self.has_property_selection(i) {
|
||||
return Task::none();
|
||||
}
|
||||
// Persist into the tab's header (CELTYPE). Resolve to a
|
||||
// handle when the name matches a line_types entry so the
|
||||
// handle-based lookup stays in sync. #21.
|
||||
|
|
@ -2361,6 +2377,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
/// attributes. Entry points: double-clicking such a block, or ATTEDIT.
|
||||
pub(crate) fn open_attribute_editor(&mut self, handle: acadrust::Handle) {
|
||||
let i = self.active_tab;
|
||||
if self.reject_locked_edit(i, handle) {
|
||||
return;
|
||||
}
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
// Ok((block, rows)) to open; Err(msg) to report and stay closed. The
|
||||
// borrow of `doc` ends with this match, before any `self` mutation.
|
||||
|
|
|
|||
|
|
@ -2320,6 +2320,7 @@ impl OpenCADStudio {
|
|||
if locked { "locked" } else { "unlocked" }
|
||||
).as_ref());
|
||||
self.sync_ribbon_layers();
|
||||
self.refresh_properties();
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
|
|
@ -3203,6 +3204,9 @@ impl OpenCADStudio {
|
|||
Message::AnnoObjectScaleToggle(name) => {
|
||||
let i = self.active_tab;
|
||||
if let Some(entity) = self.anno_object_scale_target {
|
||||
if self.tabs[i].scene.is_layer_locked(entity) {
|
||||
return Task::none();
|
||||
}
|
||||
if let Some(sh) = self.tabs[i].scene.scale_handle_ensuring(&name) {
|
||||
self.push_undo_snapshot(i, "OBJECTSCALE");
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
|
|
@ -4249,6 +4253,9 @@ impl OpenCADStudio {
|
|||
self.ribbon.close_dropdown();
|
||||
let handles = self.property_target_handles(i);
|
||||
if handles.is_empty() {
|
||||
if self.has_property_selection(i) {
|
||||
return Task::none();
|
||||
}
|
||||
// Persist into the tab's header (CELWEIGHT). #21.
|
||||
self.tabs[i].scene.document.header.current_line_weight = lw.value();
|
||||
self.tabs[i].dirty = true;
|
||||
|
|
@ -4322,6 +4329,9 @@ impl OpenCADStudio {
|
|||
let i = self.active_tab;
|
||||
let handles = self.property_target_handles(i);
|
||||
if handles.is_empty() {
|
||||
if self.has_property_selection(i) {
|
||||
return Task::none();
|
||||
}
|
||||
self.tabs[i].scene.document.header.current_line_weight = lw.value();
|
||||
self.tabs[i].dirty = true;
|
||||
self.ribbon.active_lineweight = lw;
|
||||
|
|
|
|||
|
|
@ -1079,6 +1079,14 @@ impl OpenCADStudio {
|
|||
|
||||
// ── Grip drag ─────────────────────────────────────────────
|
||||
if let Some(grip) = self.tabs[i].active_grip.clone() {
|
||||
if grip
|
||||
.targets
|
||||
.iter()
|
||||
.any(|target| self.tabs[i].scene.is_layer_locked(target.handle))
|
||||
{
|
||||
self.cancel_active_grip_edit();
|
||||
return Task::none();
|
||||
}
|
||||
let grip_started = Instant::now();
|
||||
let (vw, vh) = vp_size;
|
||||
let bounds = iced::Rectangle {
|
||||
|
|
@ -3632,8 +3640,6 @@ impl OpenCADStudio {
|
|||
bounds,
|
||||
candidate_handles.as_ref(),
|
||||
));
|
||||
// Objects on a locked layer aren't selectable.
|
||||
handles.retain(|&h| !self.tabs[i].scene.is_layer_locked(h));
|
||||
// Box/lasso accumulates like individual picks
|
||||
// (issue #83): a plain box adds to the current
|
||||
// selection, Shift+box removes the boxed
|
||||
|
|
@ -3746,7 +3752,6 @@ impl OpenCADStudio {
|
|||
.into_iter()
|
||||
.filter_map(|s| Scene::handle_from_wire_name(s))
|
||||
.filter(|&h| self.tabs[i].scene.passes_selection_filter(h))
|
||||
.filter(|&h| !self.tabs[i].scene.is_layer_locked(h))
|
||||
.collect();
|
||||
if cands.len() >= 2 {
|
||||
// Overlap: open the list box at the cursor.
|
||||
|
|
@ -3806,45 +3811,33 @@ impl OpenCADStudio {
|
|||
// Selection filter: drop a pick whose type is excluded.
|
||||
let hit = hit.filter(|&h| self.tabs[i].scene.passes_selection_filter(h));
|
||||
if let Some(handle) = hit {
|
||||
if let Some(layer) = self.tabs[i].scene.locked_layer_name(handle) {
|
||||
// Locked layer: visible + snappable but
|
||||
// not selectable. Report and do nothing
|
||||
// else — in particular do NOT set
|
||||
// `selection_just_completed`, or a
|
||||
// gather command (MOVE's "select
|
||||
// objects") would wrongly finish.
|
||||
self.command_line.push_info(crate::tf!(
|
||||
"Object is on locked layer \"{layer}\" — unlock the layer to select or edit it."
|
||||
).as_ref());
|
||||
} else {
|
||||
// Individual picks accumulate (issue #47):
|
||||
// each plain click adds to the selection,
|
||||
// Shift+click removes the picked entity.
|
||||
// PICKADD 0 (#226): a plain click
|
||||
// REPLACES the selection instead and
|
||||
// Shift+click toggles membership.
|
||||
if self.shift_down || self.select_remove_mode {
|
||||
// Remove was asked for by name, so it only
|
||||
// ever takes away — the toggle below is
|
||||
// Shift's PICKADD-0 behaviour, not its.
|
||||
if !self.select_remove_mode
|
||||
&& !selection_pick_add
|
||||
&& !self.tabs[i].scene.selected.contains(&handle)
|
||||
{
|
||||
self.tabs[i].scene.select_entity(handle, false);
|
||||
self.tabs[i].scene.expand_selection_for_groups(&[handle]);
|
||||
} else {
|
||||
self.tabs[i].scene.deselect_entity(handle);
|
||||
}
|
||||
} else {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.select_entity(handle, !selection_pick_add);
|
||||
// Individual picks accumulate (issue #47):
|
||||
// each plain click adds to the selection,
|
||||
// Shift+click removes the picked entity.
|
||||
// PICKADD 0 (#226): a plain click
|
||||
// REPLACES the selection instead and
|
||||
// Shift+click toggles membership.
|
||||
if self.shift_down || self.select_remove_mode {
|
||||
// Remove was asked for by name, so it only
|
||||
// ever takes away — the toggle below is
|
||||
// Shift's PICKADD-0 behaviour, not its.
|
||||
if !self.select_remove_mode
|
||||
&& !selection_pick_add
|
||||
&& !self.tabs[i].scene.selected.contains(&handle)
|
||||
{
|
||||
self.tabs[i].scene.select_entity(handle, false);
|
||||
self.tabs[i].scene.expand_selection_for_groups(&[handle]);
|
||||
} else {
|
||||
self.tabs[i].scene.deselect_entity(handle);
|
||||
}
|
||||
self.refresh_properties();
|
||||
selection_just_completed = true;
|
||||
} else {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.select_entity(handle, !selection_pick_add);
|
||||
self.tabs[i].scene.expand_selection_for_groups(&[handle]);
|
||||
}
|
||||
self.refresh_properties();
|
||||
selection_just_completed = true;
|
||||
} else {
|
||||
// Empty-space click only ARMS a box here; it
|
||||
// no longer clears the selection, so a box can
|
||||
|
|
@ -4011,8 +4004,6 @@ impl OpenCADStudio {
|
|||
));
|
||||
// Selection filter: keep only allowed types.
|
||||
handles.retain(|&h| self.tabs[i].scene.passes_selection_filter(h));
|
||||
// Objects on a locked layer aren't selectable.
|
||||
handles.retain(|&h| !self.tabs[i].scene.is_layer_locked(h));
|
||||
// Accumulate (issue #83): a plain box adds to the
|
||||
// current selection, Shift+box removes the boxed
|
||||
// entities. An empty box leaves the selection alone
|
||||
|
|
|
|||
Loading…
Reference in a new issue