forked from 1b-agent/opencadstudio-upstream
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied, so their list CRUD kept drifting and breaking: Dimension's New created nothing (#73), new text styles never reached the ribbon dropdown (#74), and several were added with a NULL handle (dropped on DWG save, #67). Collapse the duplicated parts into three shared pieces and leave only the per-manager property editor bespoke: - app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy / delete / rename) handling handle allocation, ribbon sync, Table re-key and name-reference rewrites in a single place. - ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy / Delete on the left, Set Current + Apply on the right), style list and chrome. Each manager now only builds its editor. - ui/style_list.rs: the list row — single click selects, double click starts an inline rename. New capabilities, now consistent across all five: - Copy duplicates the selected style. - Double-click a name to rename it inline. - Right side is always Set Current + Apply (added Table set-current; MLine apply is a documented placeholder until its editor is editable). Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
9cdb5ea5fd
commit
f2dfd01795
12 files changed
+2113
-1607
No files matched your search
+90
-58
@@ -1,19 +1,22 @@
|
||||
mod cmd_result;
|
||||
mod commands;
|
||||
pub mod plugin_host;
|
||||
mod document;
|
||||
mod expr_eval;
|
||||
mod helpers;
|
||||
mod history;
|
||||
mod layers;
|
||||
mod mtext_editor;
|
||||
mod model_ops;
|
||||
mod mtext_editor;
|
||||
pub mod plugin_host;
|
||||
mod properties;
|
||||
mod settings;
|
||||
mod style_ops;
|
||||
mod text_inline;
|
||||
mod update;
|
||||
mod view;
|
||||
|
||||
pub use style_ops::StyleKind;
|
||||
|
||||
use document::DocumentTab;
|
||||
|
||||
use crate::modules::ModuleEvent;
|
||||
@@ -382,6 +385,13 @@ pub(super) struct OpenCADStudio {
|
||||
ts_border_color: [[String; 6]; 3],
|
||||
ts_border_spacing: [[String; 6]; 3],
|
||||
|
||||
// ── Shared style-manager inline rename ────────────────────────────────
|
||||
/// Original name of the style currently being renamed inline (double-click
|
||||
/// a style name in any style manager). `None` when not renaming.
|
||||
style_rename: Option<String>,
|
||||
/// Edit buffer for the inline rename text input.
|
||||
style_rename_buf: String,
|
||||
|
||||
// ── TextStyle Font Browser ────────────────────────────────────────────
|
||||
textstyle_selected: String,
|
||||
/// Edit buffer for font file name.
|
||||
@@ -1091,6 +1101,13 @@ pub enum Message {
|
||||
TextStyleDialogSelect(String),
|
||||
TextStyleDialogSetCurrent,
|
||||
TextStyleDialogNew,
|
||||
TextStyleDialogCopy,
|
||||
// Shared inline-rename messages for every style manager. `StyleKind`
|
||||
// routes the commit to the right backing store.
|
||||
StyleRenameStart(StyleKind, String),
|
||||
StyleRenameEdit(String),
|
||||
StyleRenameCommit(StyleKind),
|
||||
StyleRenameCancel,
|
||||
TextStyleDialogDelete,
|
||||
/// Edit a string field (FontFile / Width / Oblique).
|
||||
TextStyleEdit {
|
||||
@@ -1110,14 +1127,19 @@ pub enum Message {
|
||||
TableStyleDialogClose,
|
||||
TableStyleDialogSelect(String),
|
||||
TableStyleDialogNew,
|
||||
TableStyleDialogCopy,
|
||||
TableStyleDialogDelete,
|
||||
TableStyleDialogSetCurrent,
|
||||
/// Toggle the Annotative flag on the selected table style.
|
||||
TableStyleToggleAnnotative,
|
||||
/// Toggle a boolean flag (title_suppressed / header_suppressed / flow) on
|
||||
/// the selected table style.
|
||||
TableStyleToggle(&'static str),
|
||||
/// Update a general edit buffer (hmargin / vmargin).
|
||||
TableStyleEdit { field: &'static str, value: String },
|
||||
TableStyleEdit {
|
||||
field: &'static str,
|
||||
value: String,
|
||||
},
|
||||
/// Write the general edit buffers back into the selected table style.
|
||||
TableStyleApply,
|
||||
/// Update a per-cell edit buffer (row 0=Data,1=Header,2=Title).
|
||||
@@ -1161,7 +1183,9 @@ pub enum Message {
|
||||
MlStyleDialogClose,
|
||||
MlStyleDialogSelect(String),
|
||||
MlStyleDialogSetCurrent,
|
||||
MlStyleApply,
|
||||
MlStyleDialogNew,
|
||||
MlStyleDialogCopy,
|
||||
MlStyleDialogDelete,
|
||||
// ── MLeaderStyle Dialog ───────────────────────────────────────────────
|
||||
MLeaderStyleDialogOpen,
|
||||
@@ -1170,6 +1194,7 @@ pub enum Message {
|
||||
MLeaderStyleDialogSelect(String),
|
||||
MLeaderStyleDialogSetCurrent,
|
||||
MLeaderStyleDialogNew,
|
||||
MLeaderStyleDialogCopy,
|
||||
MLeaderStyleDialogDelete,
|
||||
MLeaderStyleEdit {
|
||||
field: &'static str,
|
||||
@@ -1198,6 +1223,7 @@ pub enum Message {
|
||||
DimStyleDialogTab(u8),
|
||||
/// Create a new empty style (prompts via command line).
|
||||
DimStyleDialogNew,
|
||||
DimStyleDialogCopy,
|
||||
/// Set the selected style as the document's current dim style.
|
||||
DimStyleDialogSetCurrent,
|
||||
/// Delete the selected style.
|
||||
@@ -1362,6 +1388,8 @@ impl OpenCADStudio {
|
||||
ps_lineweight_buf: "255".to_string(),
|
||||
ps_screening_buf: "100".to_string(),
|
||||
// TextStyle font browser
|
||||
style_rename: None,
|
||||
style_rename_buf: String::new(),
|
||||
textstyle_selected: "Standard".to_string(),
|
||||
textstyle_font: String::new(),
|
||||
textstyle_width: "1.0".to_string(),
|
||||
@@ -1551,59 +1579,63 @@ impl OpenCADStudio {
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn run() -> iced::Result {
|
||||
iced::daemon(OpenCADStudio::boot, OpenCADStudio::update, OpenCADStudio::view)
|
||||
.subscription(OpenCADStudio::subscription)
|
||||
.title(|state: &OpenCADStudio, window_id: window::Id| {
|
||||
if Some(window_id) == state.layer_window {
|
||||
return "Layer Properties Manager".into();
|
||||
}
|
||||
if Some(window_id) == state.page_setup_window {
|
||||
return "Page Setup".into();
|
||||
}
|
||||
if Some(window_id) == state.textstyle_window {
|
||||
return "Text Style".into();
|
||||
}
|
||||
if Some(window_id) == state.tablestyle_window {
|
||||
return "Table Style".into();
|
||||
}
|
||||
if Some(window_id) == state.mlstyle_window {
|
||||
return "Multiline Style".into();
|
||||
}
|
||||
if Some(window_id) == state.mleaderstyle_window {
|
||||
return "Multileader Style".into();
|
||||
}
|
||||
if Some(window_id) == state.layout_manager_window {
|
||||
return "Layout Manager".into();
|
||||
}
|
||||
if Some(window_id) == state.plotstyle_window {
|
||||
return "Plot Style Table Editor".into();
|
||||
}
|
||||
if Some(window_id) == state.dimstyle_window {
|
||||
return "Dimension Style Manager".into();
|
||||
}
|
||||
if Some(window_id) == state.shortcuts_window {
|
||||
return "Keyboard Shortcuts".into();
|
||||
}
|
||||
if Some(window_id) == state.about_window {
|
||||
return "About Open CAD Studio".into();
|
||||
}
|
||||
if Some(window_id) == state.update_notice_window {
|
||||
return "Update Available".into();
|
||||
}
|
||||
if Some(window_id) == state.unsaved_dialog_window {
|
||||
return "Unsaved Changes".into();
|
||||
}
|
||||
if Some(window_id) == state.save_dialog_window {
|
||||
return "Save As".into();
|
||||
}
|
||||
if let Some(tab) = state.tabs.get(state.active_tab) {
|
||||
let dot = if tab.dirty { "● " } else { "" };
|
||||
let name = tab.tab_display_name();
|
||||
format!("{}Open CAD Studio — {}", dot, name)
|
||||
} else {
|
||||
"Open CAD Studio".to_string()
|
||||
}
|
||||
})
|
||||
.theme(|state: &OpenCADStudio, _| state.active_theme.clone())
|
||||
.run()
|
||||
iced::daemon(
|
||||
OpenCADStudio::boot,
|
||||
OpenCADStudio::update,
|
||||
OpenCADStudio::view,
|
||||
)
|
||||
.subscription(OpenCADStudio::subscription)
|
||||
.title(|state: &OpenCADStudio, window_id: window::Id| {
|
||||
if Some(window_id) == state.layer_window {
|
||||
return "Layer Properties Manager".into();
|
||||
}
|
||||
if Some(window_id) == state.page_setup_window {
|
||||
return "Page Setup".into();
|
||||
}
|
||||
if Some(window_id) == state.textstyle_window {
|
||||
return "Text Style".into();
|
||||
}
|
||||
if Some(window_id) == state.tablestyle_window {
|
||||
return "Table Style".into();
|
||||
}
|
||||
if Some(window_id) == state.mlstyle_window {
|
||||
return "Multiline Style".into();
|
||||
}
|
||||
if Some(window_id) == state.mleaderstyle_window {
|
||||
return "Multileader Style".into();
|
||||
}
|
||||
if Some(window_id) == state.layout_manager_window {
|
||||
return "Layout Manager".into();
|
||||
}
|
||||
if Some(window_id) == state.plotstyle_window {
|
||||
return "Plot Style Table Editor".into();
|
||||
}
|
||||
if Some(window_id) == state.dimstyle_window {
|
||||
return "Dimension Style Manager".into();
|
||||
}
|
||||
if Some(window_id) == state.shortcuts_window {
|
||||
return "Keyboard Shortcuts".into();
|
||||
}
|
||||
if Some(window_id) == state.about_window {
|
||||
return "About Open CAD Studio".into();
|
||||
}
|
||||
if Some(window_id) == state.update_notice_window {
|
||||
return "Update Available".into();
|
||||
}
|
||||
if Some(window_id) == state.unsaved_dialog_window {
|
||||
return "Unsaved Changes".into();
|
||||
}
|
||||
if Some(window_id) == state.save_dialog_window {
|
||||
return "Save As".into();
|
||||
}
|
||||
if let Some(tab) = state.tabs.get(state.active_tab) {
|
||||
let dot = if tab.dirty { "● " } else { "" };
|
||||
let name = tab.tab_display_name();
|
||||
format!("{}Open CAD Studio — {}", dot, name)
|
||||
} else {
|
||||
"Open CAD Studio".to_string()
|
||||
}
|
||||
})
|
||||
.theme(|state: &OpenCADStudio, _| state.active_theme.clone())
|
||||
.run()
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
//! Shared CRUD layer for every style manager (text / dimension / table /
|
||||
//! multileader / multiline).
|
||||
//!
|
||||
//! The five managers all expose the same list operations — New, Copy, Delete,
|
||||
//! Rename, Set-Current — over a named collection of styles. Only the *property
|
||||
//! editor* and the *storage backend* differ, so those are the only parts kept
|
||||
//! per-manager:
|
||||
//!
|
||||
//! * **Table-backed** (text, dim): live in `Table<T>`, keyed by upper-cased
|
||||
//! name. Renaming must re-key the entry and rewrite name-based entity
|
||||
//! references (TEXT/MTEXT `style`, DIMENSION `style_name`).
|
||||
//! * **Object-backed** (table, multileader, multiline): live in
|
||||
//! `document.objects`, keyed by handle. Renaming only mutates the `name`
|
||||
//! field; entities reference these by handle, so nothing else moves.
|
||||
//!
|
||||
//! Centralising the flow here is what fixes the bug class that kept recurring
|
||||
//! when each manager was hand-copied: a dead New, a missing ribbon refresh, a
|
||||
//! style added without a handle (dropped on DWG save, issue #67).
|
||||
|
||||
use super::OpenCADStudio;
|
||||
use acadrust::objects::{MLineStyle, MultiLeaderStyle, ObjectType, TableStyle};
|
||||
use acadrust::tables::{DimStyle, TextStyle};
|
||||
use acadrust::types::Handle;
|
||||
|
||||
/// Which style manager an operation targets. Carried by the shared rename
|
||||
/// messages so one handler can dispatch to the right storage.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StyleKind {
|
||||
Text,
|
||||
Dim,
|
||||
Table,
|
||||
MLeader,
|
||||
MLine,
|
||||
}
|
||||
|
||||
impl StyleKind {
|
||||
/// True when this style feeds the ribbon's quick-set dropdown.
|
||||
fn in_ribbon(self) -> bool {
|
||||
!matches!(self, StyleKind::MLine)
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
// ── Queries ────────────────────────────────────────────────────────────
|
||||
|
||||
/// All style names for `kind`, in display order (object-backed styles are
|
||||
/// sorted by name so the `HashMap` backing them renders stably).
|
||||
pub(super) fn style_names(&self, kind: StyleKind) -> Vec<String> {
|
||||
let doc = &self.tabs[self.active_tab].scene.document;
|
||||
let mut from_objects = |pick: fn(&ObjectType) -> Option<&str>| -> Vec<String> {
|
||||
let mut v: Vec<String> = doc
|
||||
.objects
|
||||
.values()
|
||||
.filter_map(pick)
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
v.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
|
||||
v
|
||||
};
|
||||
match kind {
|
||||
StyleKind::Text => doc.text_styles.iter().map(|s| s.name.clone()).collect(),
|
||||
StyleKind::Dim => doc.dim_styles.iter().map(|s| s.name.clone()).collect(),
|
||||
StyleKind::Table => from_objects(|o| match o {
|
||||
ObjectType::TableStyle(s) => Some(s.name.as_str()),
|
||||
_ => None,
|
||||
}),
|
||||
StyleKind::MLeader => from_objects(|o| match o {
|
||||
ObjectType::MultiLeaderStyle(s) => Some(s.name.as_str()),
|
||||
_ => None,
|
||||
}),
|
||||
StyleKind::MLine => from_objects(|o| match o {
|
||||
ObjectType::MLineStyle(s) => Some(s.name.as_str()),
|
||||
_ => None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn style_selected(&self, kind: StyleKind) -> String {
|
||||
match kind {
|
||||
StyleKind::Text => self.textstyle_selected.clone(),
|
||||
StyleKind::Dim => self.dimstyle_selected.clone(),
|
||||
StyleKind::Table => self.tablestyle_selected.clone(),
|
||||
StyleKind::MLeader => self.mleaderstyle_selected.clone(),
|
||||
StyleKind::MLine => self.mlstyle_selected.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_style_selected(&mut self, kind: StyleKind, name: String) {
|
||||
match kind {
|
||||
StyleKind::Text => self.textstyle_selected = name,
|
||||
StyleKind::Dim => self.dimstyle_selected = name,
|
||||
StyleKind::Table => self.tablestyle_selected = name,
|
||||
StyleKind::MLeader => self.mleaderstyle_selected = name,
|
||||
StyleKind::MLine => self.mlstyle_selected = name,
|
||||
}
|
||||
}
|
||||
|
||||
fn style_exists(&self, kind: StyleKind, name: &str) -> bool {
|
||||
self.style_names(kind)
|
||||
.iter()
|
||||
.any(|n| n.eq_ignore_ascii_case(name))
|
||||
}
|
||||
|
||||
/// First free `Style{n}` name for a fresh style.
|
||||
fn unique_new_name(&self, kind: StyleKind) -> String {
|
||||
(1u32..)
|
||||
.map(|n| format!("Style{n}"))
|
||||
.find(|c| !self.style_exists(kind, c))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// First free `{base} ({n})` name for a copy / disambiguated entry.
|
||||
fn unique_suffixed_name(&self, kind: StyleKind, base: &str) -> String {
|
||||
(1u32..)
|
||||
.map(|n| format!("{base} ({n})"))
|
||||
.find(|c| !self.style_exists(kind, c))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// ── Per-manager glue (the only kind-specific list code) ────────────────
|
||||
|
||||
/// Reload the property-editor buffers for the kinds that have them.
|
||||
fn load_style_bufs(&mut self, kind: StyleKind) {
|
||||
let i = self.active_tab;
|
||||
match kind {
|
||||
StyleKind::Text => self.load_textstyle_bufs(i),
|
||||
StyleKind::Dim => self.load_dimstyle_bufs(i),
|
||||
StyleKind::MLeader => self.load_mleaderstyle_bufs(i),
|
||||
StyleKind::Table | StyleKind::MLine => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh anything that mirrors the style list / current style after a
|
||||
/// mutation (ribbon dropdowns, geometry that depends on the style).
|
||||
fn after_style_change(&mut self, kind: StyleKind) {
|
||||
if kind.in_ribbon() {
|
||||
self.sync_ribbon_styles();
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_default_style(&mut self, kind: StyleKind, name: &str, handle: Handle) {
|
||||
let doc = &mut self.tabs[self.active_tab].scene.document;
|
||||
match kind {
|
||||
StyleKind::Text => {
|
||||
let mut s = TextStyle::new(name);
|
||||
s.handle = handle;
|
||||
let _ = doc.text_styles.add(s);
|
||||
}
|
||||
StyleKind::Dim => {
|
||||
let mut s = DimStyle::new(name);
|
||||
s.handle = handle;
|
||||
let _ = doc.dim_styles.add(s);
|
||||
}
|
||||
StyleKind::Table => {
|
||||
let mut s = TableStyle::standard();
|
||||
s.name = name.to_string();
|
||||
s.handle = handle;
|
||||
doc.objects.insert(handle, ObjectType::TableStyle(s));
|
||||
}
|
||||
StyleKind::MLeader => {
|
||||
let mut s = MultiLeaderStyle::new(name);
|
||||
s.handle = handle;
|
||||
doc.objects.insert(handle, ObjectType::MultiLeaderStyle(s));
|
||||
}
|
||||
StyleKind::MLine => {
|
||||
let mut s = MLineStyle::standard();
|
||||
s.name = name.to_string();
|
||||
s.handle = handle;
|
||||
doc.objects.insert(handle, ObjectType::MLineStyle(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone the style named `src` under `name` with a fresh `handle`.
|
||||
/// Returns false if `src` no longer exists.
|
||||
fn clone_style_as(&mut self, kind: StyleKind, src: &str, name: &str, handle: Handle) -> bool {
|
||||
let doc = &mut self.tabs[self.active_tab].scene.document;
|
||||
match kind {
|
||||
StyleKind::Text => {
|
||||
if let Some(mut s) = doc.text_styles.get(src).cloned() {
|
||||
s.name = name.to_string();
|
||||
s.handle = handle;
|
||||
let _ = doc.text_styles.add(s);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
StyleKind::Dim => {
|
||||
if let Some(mut s) = doc.dim_styles.get(src).cloned() {
|
||||
s.name = name.to_string();
|
||||
s.handle = handle;
|
||||
let _ = doc.dim_styles.add(s);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
StyleKind::Table => {
|
||||
if let Some(mut s) = find_object_style(doc, src, |o| match o {
|
||||
ObjectType::TableStyle(s) => Some((s.name.as_str(), s.clone())),
|
||||
_ => None,
|
||||
}) {
|
||||
s.name = name.to_string();
|
||||
s.handle = handle;
|
||||
doc.objects.insert(handle, ObjectType::TableStyle(s));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
StyleKind::MLeader => {
|
||||
if let Some(mut s) = find_object_style(doc, src, |o| match o {
|
||||
ObjectType::MultiLeaderStyle(s) => Some((s.name.as_str(), s.clone())),
|
||||
_ => None,
|
||||
}) {
|
||||
s.name = name.to_string();
|
||||
s.handle = handle;
|
||||
doc.objects.insert(handle, ObjectType::MultiLeaderStyle(s));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
StyleKind::MLine => {
|
||||
if let Some(mut s) = find_object_style(doc, src, |o| match o {
|
||||
ObjectType::MLineStyle(s) => Some((s.name.as_str(), s.clone())),
|
||||
_ => None,
|
||||
}) {
|
||||
s.name = name.to_string();
|
||||
s.handle = handle;
|
||||
doc.objects.insert(handle, ObjectType::MLineStyle(s));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn remove_style_storage(&mut self, kind: StyleKind, name: &str) -> bool {
|
||||
let doc = &mut self.tabs[self.active_tab].scene.document;
|
||||
match kind {
|
||||
StyleKind::Text => doc.text_styles.remove(name).is_some(),
|
||||
StyleKind::Dim => doc.dim_styles.remove(name).is_some(),
|
||||
StyleKind::Table | StyleKind::MLeader | StyleKind::MLine => {
|
||||
let kind2 = kind;
|
||||
if let Some(h) = object_handle(doc, name, kind2) {
|
||||
doc.objects.remove(&h).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rename `old`→`new` in the backing store, re-keying table entries and
|
||||
/// rewriting name-based references + current-style pointers.
|
||||
fn rename_style_storage(&mut self, kind: StyleKind, old: &str, new: &str) {
|
||||
let i = self.active_tab;
|
||||
match kind {
|
||||
StyleKind::Text => {
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
if let Some(mut s) = doc.text_styles.get(old).cloned() {
|
||||
s.name = new.to_string();
|
||||
if !s.handle.is_valid() {
|
||||
s.handle = doc.allocate_handle();
|
||||
}
|
||||
let _ = doc.text_styles.add(s);
|
||||
}
|
||||
doc.text_styles.remove(old);
|
||||
if doc.header.current_text_style_name.eq_ignore_ascii_case(old) {
|
||||
doc.header.current_text_style_name = new.to_string();
|
||||
}
|
||||
for e in doc.entities_mut() {
|
||||
match e {
|
||||
acadrust::entities::EntityType::Text(t)
|
||||
if t.style.eq_ignore_ascii_case(old) =>
|
||||
{
|
||||
t.style = new.to_string();
|
||||
}
|
||||
acadrust::entities::EntityType::MText(t)
|
||||
if t.style.eq_ignore_ascii_case(old) =>
|
||||
{
|
||||
t.style = new.to_string();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
StyleKind::Dim => {
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
if let Some(mut s) = doc.dim_styles.get(old).cloned() {
|
||||
s.name = new.to_string();
|
||||
if !s.handle.is_valid() {
|
||||
s.handle = doc.allocate_handle();
|
||||
}
|
||||
let _ = doc.dim_styles.add(s);
|
||||
}
|
||||
doc.dim_styles.remove(old);
|
||||
if doc.header.current_dimstyle_name.eq_ignore_ascii_case(old) {
|
||||
doc.header.current_dimstyle_name = new.to_string();
|
||||
}
|
||||
for e in doc.entities_mut() {
|
||||
if let acadrust::entities::EntityType::Dimension(d) = e {
|
||||
if d.base().style_name.eq_ignore_ascii_case(old) {
|
||||
d.base_mut().style_name = new.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StyleKind::Table => {
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
if let Some(h) = object_handle(doc, old, kind) {
|
||||
if let Some(ObjectType::TableStyle(s)) = doc.objects.get_mut(&h) {
|
||||
s.name = new.to_string();
|
||||
}
|
||||
}
|
||||
if self.ribbon.active_table_style.eq_ignore_ascii_case(old) {
|
||||
self.ribbon.active_table_style = new.to_string();
|
||||
}
|
||||
}
|
||||
StyleKind::MLeader => {
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
if let Some(h) = object_handle(doc, old, kind) {
|
||||
if let Some(ObjectType::MultiLeaderStyle(s)) = doc.objects.get_mut(&h) {
|
||||
s.name = new.to_string();
|
||||
}
|
||||
}
|
||||
if self.tabs[i].active_mleader_style.eq_ignore_ascii_case(old) {
|
||||
self.tabs[i].active_mleader_style = new.to_string();
|
||||
}
|
||||
if self.ribbon.active_mleader_style.eq_ignore_ascii_case(old) {
|
||||
self.ribbon.active_mleader_style = new.to_string();
|
||||
}
|
||||
}
|
||||
StyleKind::MLine => {
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
if let Some(h) = object_handle(doc, old, kind) {
|
||||
if let Some(ObjectType::MLineStyle(s)) = doc.objects.get_mut(&h) {
|
||||
s.name = new.to_string();
|
||||
}
|
||||
}
|
||||
if doc.header.multiline_style.eq_ignore_ascii_case(old) {
|
||||
doc.header.multiline_style = new.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Public operations (called by the message handlers) ─────────────────
|
||||
|
||||
pub(super) fn style_new(&mut self, kind: StyleKind) {
|
||||
let i = self.active_tab;
|
||||
let name = self.unique_new_name(kind);
|
||||
self.push_undo_snapshot(i, "STYLE NEW");
|
||||
let h = self.tabs[i].scene.document.allocate_handle();
|
||||
self.insert_default_style(kind, &name, h);
|
||||
self.set_style_selected(kind, name.clone());
|
||||
self.load_style_bufs(kind);
|
||||
self.tabs[i].dirty = true;
|
||||
self.after_style_change(kind);
|
||||
self.command_line
|
||||
.push_output(&format!("Style '{name}' created."));
|
||||
}
|
||||
|
||||
pub(super) fn style_copy(&mut self, kind: StyleKind) {
|
||||
let i = self.active_tab;
|
||||
let src = self.style_selected(kind);
|
||||
let name = self.unique_suffixed_name(kind, &src);
|
||||
self.push_undo_snapshot(i, "STYLE COPY");
|
||||
let h = self.tabs[i].scene.document.allocate_handle();
|
||||
if !self.clone_style_as(kind, &src, &name, h) {
|
||||
return;
|
||||
}
|
||||
self.set_style_selected(kind, name.clone());
|
||||
self.load_style_bufs(kind);
|
||||
self.tabs[i].dirty = true;
|
||||
self.after_style_change(kind);
|
||||
self.command_line
|
||||
.push_output(&format!("Style '{name}' created."));
|
||||
}
|
||||
|
||||
pub(super) fn style_delete(&mut self, kind: StyleKind) {
|
||||
let i = self.active_tab;
|
||||
let name = self.style_selected(kind);
|
||||
if name.eq_ignore_ascii_case("Standard") {
|
||||
self.command_line
|
||||
.push_error("Cannot delete the Standard style.");
|
||||
return;
|
||||
}
|
||||
self.push_undo_snapshot(i, "STYLE DEL");
|
||||
if !self.remove_style_storage(kind, &name) {
|
||||
return;
|
||||
}
|
||||
let first = self
|
||||
.style_names(kind)
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| "Standard".to_string());
|
||||
self.set_style_selected(kind, first);
|
||||
self.load_style_bufs(kind);
|
||||
self.tabs[i].dirty = true;
|
||||
self.after_style_change(kind);
|
||||
self.command_line
|
||||
.push_output(&format!("Style '{name}' deleted."));
|
||||
}
|
||||
|
||||
/// Begin inline rename of the double-clicked style.
|
||||
pub(super) fn style_rename_start(&mut self, kind: StyleKind, name: String) {
|
||||
self.set_style_selected(kind, name.clone());
|
||||
self.load_style_bufs(kind);
|
||||
self.style_rename_buf = name.clone();
|
||||
self.style_rename = Some(name);
|
||||
}
|
||||
|
||||
/// Commit the inline rename. No-op (with feedback) on empty / unchanged /
|
||||
/// colliding names, and the Standard style cannot be renamed.
|
||||
pub(super) fn style_rename_commit(&mut self, kind: StyleKind) {
|
||||
let i = self.active_tab;
|
||||
let Some(old) = self.style_rename.take() else {
|
||||
return;
|
||||
};
|
||||
let new = self.style_rename_buf.trim().to_string();
|
||||
self.style_rename_buf.clear();
|
||||
if new.is_empty() || new.eq_ignore_ascii_case(&old) {
|
||||
return;
|
||||
}
|
||||
if old.eq_ignore_ascii_case("Standard") {
|
||||
self.command_line
|
||||
.push_error("Cannot rename the Standard style.");
|
||||
return;
|
||||
}
|
||||
if self.style_exists(kind, &new) {
|
||||
self.command_line
|
||||
.push_error(&format!("Style '{new}' already exists."));
|
||||
return;
|
||||
}
|
||||
self.push_undo_snapshot(i, "STYLE RENAME");
|
||||
self.rename_style_storage(kind, &old, &new);
|
||||
if self.style_selected(kind).eq_ignore_ascii_case(&old) {
|
||||
self.set_style_selected(kind, new.clone());
|
||||
}
|
||||
self.load_style_bufs(kind);
|
||||
self.tabs[i].dirty = true;
|
||||
self.after_style_change(kind);
|
||||
self.command_line
|
||||
.push_output(&format!("Renamed '{old}' → '{new}'."));
|
||||
}
|
||||
|
||||
pub(super) fn style_rename_cancel(&mut self) {
|
||||
self.style_rename = None;
|
||||
self.style_rename_buf.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Object-store helpers ───────────────────────────────────────────────────
|
||||
|
||||
/// Find the object-backed style named `name` and return a clone. `pick` maps a
|
||||
/// matching variant to `(its name, a clone of the inner style)`.
|
||||
fn find_object_style<T>(
|
||||
doc: &acadrust::CadDocument,
|
||||
name: &str,
|
||||
pick: impl Fn(&ObjectType) -> Option<(&str, T)>,
|
||||
) -> Option<T> {
|
||||
doc.objects.values().find_map(|o| {
|
||||
let (n, val) = pick(o)?;
|
||||
n.eq_ignore_ascii_case(name).then_some(val)
|
||||
})
|
||||
}
|
||||
|
||||
fn object_handle(doc: &acadrust::CadDocument, name: &str, kind: StyleKind) -> Option<Handle> {
|
||||
doc.objects.iter().find_map(|(&h, o)| {
|
||||
let matches = match (kind, o) {
|
||||
(StyleKind::Table, ObjectType::TableStyle(s)) => s.name.eq_ignore_ascii_case(name),
|
||||
(StyleKind::MLeader, ObjectType::MultiLeaderStyle(s)) => {
|
||||
s.name.eq_ignore_ascii_case(name)
|
||||
}
|
||||
(StyleKind::MLine, ObjectType::MLineStyle(s)) => s.name.eq_ignore_ascii_case(name),
|
||||
_ => false,
|
||||
};
|
||||
matches.then_some(h)
|
||||
})
|
||||
}
|
||||
+246
-471
File diff suppressed because it is too large.
Load diff
+515
-195
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user