REFEDIT: Edit Reference dialog and unique names for xref objects

REFEDIT opens a dialog after the pick: the nesting chain as chips with a
preview of the chosen definition, all objects or picking them in the
drawing, attribute definitions (offered only when the definition has
them), locking objects outside the working set and unique names for an
external reference's named objects. The settings are remembered.

With unique names an xref's dependent layers, linetypes, text styles and
blocks are checked out as $N$NAME records of their own; without, under
the plain name, sharing a host record of that name. Save writes them back
under the reference's names, and the records made for the session are
removed when it closes. -REFEDIT uses the remembered settings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-14 10:39:28 +03:00
1 parent 592289f4fd
commit 92a3a8ee4d
33 files changed
+1061 -22

No files matched your search

+1
View File
@@ -29,6 +29,7 @@ pub mod plot;
pub mod plugin_manager;
pub mod print_all;
pub mod recovery;
pub mod refedit_dialog;
pub mod sheet_set;
pub mod shortcuts;
pub mod update_notice;
+1 -1
View File
@@ -302,7 +302,7 @@ pub(crate) fn well_style(theme: &Theme) -> container::Style {
}
/// A titled card.
fn card<'a>(title: String, content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
pub(crate) fn card<'a>(title: String, content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
container(
column![
text(title.to_uppercase()).size(10).style(accent_text),
+247
View File
@@ -0,0 +1,247 @@
//! Edit Reference dialog (REFEDIT): the nesting level to check out, shown as
//! a chain of chips beside a preview of the chosen definition, how the
//! working set is filled, whether attribute definitions come along, and the
//! session settings (lock what is outside the working set, give clashing
//! named objects of an external reference unique names).
use iced::widget::{button, column, container, row, text, toggler, Space};
use iced::{Border, Element, Fill, Length, Theme};
use crate::app::Message;
use crate::scene::model::wire_model::WireModel;
use crate::ui::style::common::muted_style;
use crate::ui::style::form::{button_style, dialog_button};
use crate::ui::window::pdf_dialogs::{card, well_style};
/// One edit in the Edit Reference dialog.
#[derive(Debug, Clone)]
pub enum RefEditDialogMsg {
/// The nesting level picked in the chain (0 = the selected reference).
Level(usize),
/// Pick the working set's objects in the drawing (else all of them).
Nested(bool),
Attributes(bool),
Lock(bool),
Unique(bool),
Help,
Ok,
}
fn msg(m: RefEditDialogMsg) -> Message {
Message::RefEditDialog(m)
}
/// One level of the nesting chain as the dialog shows it.
pub struct RefEditLevel {
/// The reference's handle: the picked object for level 0, the
/// definition-owned reference below it.
pub insert: codec::Handle,
pub name: String,
/// The definition's preview.
pub wires: Vec<WireModel>,
/// The definition holds attribute definitions (the attribute switch is
/// only offered then).
pub has_attdefs: bool,
/// The referenced file of an external reference.
pub xref_path: Option<String>,
}
pub struct RefEditDialogState {
pub top: codec::Handle,
/// The picked chain, outermost first.
pub chain: Vec<crate::modules::draw::modify::refedit::ChainLevel>,
pub levels: Vec<RefEditLevel>,
pub level: usize,
pub nested: bool,
pub attributes: bool,
pub lock: bool,
pub unique: bool,
}
impl RefEditDialogState {
/// The attribute switch counts only where the chosen definition has
/// attribute definitions.
pub fn attributes_enabled(&self) -> bool {
self.levels.get(self.level).is_some_and(|l| l.has_attdefs)
}
}
fn label<'a>(value: String, enabled: bool) -> Element<'a, Message> {
text(value)
.size(12)
.style(move |theme: &Theme| {
if enabled {
text::Style::default()
} else {
text::Style {
color: Some(theme.palette().background.strong.color),
}
}
})
.width(Fill)
.into()
}
/// A labelled switch on one row; inactive while `on_toggle` is None.
fn switch_row<'a>(
name: String,
value: bool,
on_toggle: Option<fn(bool) -> RefEditDialogMsg>,
) -> Element<'a, Message> {
let enabled = on_toggle.is_some();
let mut switch = toggler(value).size(18.0);
if let Some(on) = on_toggle {
switch = switch.on_toggle(move |v| msg(on(v)));
}
row![label(name, enabled), switch]
.spacing(8)
.align_y(iced::Center)
.into()
}
/// A level of the chain as a pill; the chosen one framed in the accent colour.
fn level_chip<'a>(name: &str, index: usize, chosen: bool) -> Element<'a, Message> {
button(text(name.to_string()).size(12))
.on_press(msg(RefEditDialogMsg::Level(index)))
.padding([5, 12])
.style(move |theme: &Theme, status| {
let mut style = button_style(false)(theme, status);
style.border = Border {
width: if chosen { 1.5 } else { 1.0 },
radius: 14.0.into(),
color: if chosen {
theme.palette().primary.base.color
} else {
theme.palette().background.strong.color
},
};
if chosen {
style.text_color = theme.palette().primary.base.color;
}
style
})
.into()
}
pub fn view<'a>(
state: &'a RefEditDialogState,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let current = state.levels.get(state.level);
let preview: Element<'a, Message> = container(match current {
Some(level) => crate::ui::window::block_palette::preview_canvas(&level.wires, 104.0),
None => Space::new().into(),
})
.width(Length::Fixed(150.0))
.height(Length::Fixed(104.0))
.padding(4)
.style(well_style)
.into();
let mut chips = row![].spacing(6).align_y(iced::Center);
for (index, level) in state.levels.iter().enumerate() {
if index > 0 {
chips = chips.push(text("›").size(13).style(muted_style));
}
chips = chips.push(level_chip(&level.name, index, index == state.level));
}
let mut about = column![text(crate::tr!("ref-edit", "dialog-chain-hint"))
.size(11)
.style(muted_style)]
.spacing(4);
if let Some(path) = current.and_then(|l| l.xref_path.as_deref()) {
// A long path keeps its end, where the file name is.
const MAX: usize = 64;
let count = path.chars().count();
let shown = if count > MAX {
format!("…{}", path.chars().skip(count - (MAX - 1)).collect::<String>())
} else {
path.to_string()
};
about = about.push(
text(format!("{} {}", crate::tr!("ref-edit", "dialog-path"), shown))
.size(11)
.style(muted_style),
);
}
let reference = card(
crate::tr!("ref-edit", "dialog-reference"),
row![
preview,
column![iced::widget::scrollable(chips).direction(
iced::widget::scrollable::Direction::Horizontal(
iced::widget::scrollable::Scrollbar::new().width(4).scroller_width(4)
)
), about]
.spacing(8)
.width(Fill)
]
.spacing(12),
);
let segment = |nested: bool, name: String| {
button(text(name).size(12).width(Fill).align_x(iced::Center))
.on_press(msg(RefEditDialogMsg::Nested(nested)))
.style(button_style(state.nested == nested))
.padding([6, 8])
.width(Fill)
};
let method = container(
row![
segment(false, crate::tr!("ref-edit", "dialog-all-objects")),
segment(true, crate::tr!("ref-edit", "dialog-pick-objects")),
]
.spacing(2),
)
.padding(2)
.width(Fill)
.style(well_style);
let attributes_enabled = state.attributes_enabled();
let working_set = card(
crate::tr!("ref-edit", "dialog-working-set"),
column![
method,
switch_row(
crate::tr!("ref-edit", "dialog-attributes"),
state.attributes && attributes_enabled,
attributes_enabled.then_some(RefEditDialogMsg::Attributes as fn(bool) -> _),
),
]
.spacing(10),
);
let session = card(
crate::tr!("ref-edit", "dialog-session"),
column![
switch_row(
crate::tr!("ref-edit", "dialog-lock"),
state.lock,
Some(RefEditDialogMsg::Lock),
),
switch_row(
crate::tr!("ref-edit", "dialog-unique"),
state.unique,
Some(RefEditDialogMsg::Unique),
),
]
.spacing(10),
);
let footer = row![
button(text("?").size(12))
.on_press(msg(RefEditDialogMsg::Help))
.style(button_style(false))
.padding([5, 11]),
Space::new().width(Fill),
dialog_button(crate::t!("Cancel"), Message::CloseModal, false),
dialog_button(
std::borrow::Cow::Owned(crate::tr!("ref-edit", "dialog-start")),
msg(RefEditDialogMsg::Ok),
true
),
]
.spacing(6)
.align_y(iced::Center);
column![reference, working_set, session, footer]
.spacing(10)
.padding([10, 12])
.width(sizing.width)
.into()
}