Skip to main content

ironcalc_base/user_model/
named_cell_styles.rs

1use crate::{
2    types::{Style, StyleIncludes},
3    user_model::history::Diff,
4    UserModel,
5};
6
7impl<'a> UserModel<'a> {
8    /// Returns the list of all named style names.
9    pub fn get_named_style_list(&self) -> Vec<String> {
10        self.model.get_named_style_list()
11    }
12
13    /// Returns the `Style` associated with the named style.
14    pub fn get_named_style(&self, name: &str) -> Result<Style, String> {
15        self.model.get_named_style(name)
16    }
17
18    /// Creates a new named style. `includes` selects which formatting
19    /// categories the style carries (Excel's "Style Includes" checkboxes);
20    /// use `StyleIncludes::default()` for a full style.
21    /// Fails if a style with that name already exists.
22    pub fn create_named_style(
23        &mut self,
24        name: &str,
25        style: &Style,
26        includes: StyleIncludes,
27    ) -> Result<(), String> {
28        self.model.create_named_style(name, style, includes)?;
29        self.push_diff_list(vec![Diff::CreateNamedStyle {
30            name: name.to_string(),
31            style: Box::new(style.clone()),
32            includes,
33        }]);
34        Ok(())
35    }
36
37    /// Deletes a named style. Fails if the style does not exist or is built-in.
38    /// Cells that used this style keep their formatting.
39    pub fn delete_named_style(&mut self, name: &str) -> Result<(), String> {
40        let old_xf_id = self.model.workbook.styles.get_xf_id_by_name(name)?;
41        self.model.delete_named_style(name)?;
42        self.push_diff_list(vec![Diff::DeleteNamedStyle {
43            name: name.to_string(),
44            old_xf_id,
45        }]);
46        Ok(())
47    }
48
49    /// Updates the formatting, the included categories and optionally the
50    /// name of a named style.
51    /// All cells, rows, and columns using the style pick up the new formatting.
52    /// Fails if the style does not exist, is built-in, or if `new_name` is already taken.
53    pub fn update_named_style(
54        &mut self,
55        name: &str,
56        new_name: &str,
57        style: &Style,
58        includes: StyleIncludes,
59    ) -> Result<(), String> {
60        let old_style = self.model.get_named_style(name)?;
61        let old_includes = self.model.get_named_style_includes(name)?;
62        self.model
63            .update_named_style(name, new_name, style, includes)?;
64        self.push_diff_list(vec![Diff::UpdateNamedStyle {
65            name: name.to_string(),
66            new_name: new_name.to_string(),
67            old_style: Box::new(old_style),
68            new_style: Box::new(style.clone()),
69            old_includes,
70            new_includes: includes,
71        }]);
72        Ok(())
73    }
74
75    /// Returns which formatting categories the named style includes.
76    pub fn get_named_style_includes(&self, name: &str) -> Result<StyleIncludes, String> {
77        self.model.get_named_style_includes(name)
78    }
79
80    /// Returns all Excel built-in named styles as `(name, Style)` pairs.
81    /// These are static definitions that are not stored in the workbook until a user applies one.
82    pub fn get_builtin_named_styles(&self) -> Vec<(String, Style)> {
83        crate::builtin_styles::builtin_named_styles()
84            .into_iter()
85            .map(|(name, style, _)| (name, style))
86            .collect()
87    }
88
89    /// Applies a named style (custom or built-in) to the current selection.
90    ///
91    /// If the style is not in the model but is a known built-in, it is first added to the
92    /// model's style table, then applied to every cell in the selection with undo support.
93    pub fn on_apply_named_style(&mut self, name: &str) -> Result<(), String> {
94        let mut diff_list = Vec::new();
95
96        // Ensure the style exists in the model, adding it from builtins if needed.
97        // Only fall back to the builtins when the name is genuinely absent;
98        // errors on an existing style (e.g. an invalid xf id in a malformed
99        // workbook) must surface as-is.
100        if self.model.workbook.styles.get_xf_id_by_name(name).is_err() {
101            let (style, includes) = crate::builtin_styles::get_builtin_style(name)
102                .ok_or_else(|| format!("Named style '{name}' not found"))?;
103            self.model
104                .workbook
105                .styles
106                .create_named_style(name, &style, includes)?;
107            diff_list.push(Diff::CreateNamedStyle {
108                name: name.to_string(),
109                style: Box::new(style),
110                includes,
111            });
112        }
113
114        // Resolve the selection range.
115        let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
116            view.sheet
117        } else {
118            return Ok(());
119        };
120        let range = if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
121            if let Some(view) = worksheet.views.get(&self.model.view_id) {
122                view.range
123            } else {
124                return Ok(());
125            }
126        } else {
127            return Ok(());
128        };
129
130        let [row_start, column_start, row_end, column_end] = range;
131        for row in row_start..=row_end {
132            for column in column_start..=column_end {
133                let old_value = self.model.get_cell_style_or_none(sheet, row, column)?;
134                // Merge per cell: only the categories the style includes are
135                // stamped, the rest of the cell's formatting is kept.
136                let current_index = self.model.workbook.worksheet(sheet)?.get_style(row, column);
137                let style_index = self
138                    .model
139                    .workbook
140                    .styles
141                    .get_style_index_for_applied_style(name, current_index)?;
142                self.model.workbook.worksheet_mut(sheet)?.set_cell_style(
143                    row,
144                    column,
145                    style_index,
146                )?;
147                diff_list.push(Diff::ApplyNamedStyle {
148                    sheet,
149                    row,
150                    column,
151                    old_value: Box::new(old_value),
152                    name: name.to_string(),
153                });
154            }
155        }
156        self.push_diff_list(diff_list);
157        Ok(())
158    }
159}