Skip to main content

ironcalc_base/user_model/
conditional_formatting.rs

1use crate::{
2    cf_types::{CfRule, CfRuleInput, ConditionalFormattingView},
3    types::Dxf,
4};
5
6use super::{common::UserModel, history::Diff};
7
8impl<'a> UserModel<'a> {
9    /// Returns all CF rules for the given sheet, sorted by priority descending.
10    ///
11    /// Each entry carries its storage `index`; see
12    /// [`crate::Model::get_conditional_formatting_list`].
13    pub fn get_conditional_formatting_list(
14        &self,
15        sheet: u32,
16    ) -> Result<Vec<ConditionalFormattingView>, String> {
17        self.model.get_conditional_formatting_list(sheet)
18    }
19
20    /// Returns the differential format (Dxf) for the CF rule at `index` on `sheet`,
21    /// or `None` if the rule type has no associated dxf (e.g. ColorScale, DataBar).
22    pub fn get_dxf_for_conditional_formatting(
23        &self,
24        sheet: u32,
25        index: u32,
26    ) -> Result<Option<Dxf>, String> {
27        self.model
28            .get_dxf_for_conditional_formatting(sheet, index as usize)
29    }
30
31    /// Adds a new CF rule to `sheet`.
32    pub fn add_conditional_formatting(
33        &mut self,
34        sheet: u32,
35        range: &str,
36        rule: CfRuleInput,
37    ) -> Result<(), String> {
38        let priority = self.model.add_conditional_formatting(sheet, range, rule)?;
39        // Read back the stored entry so the Diff contains the dxf_id that was assigned.
40        let stored_rule = self
41            .model
42            .workbook
43            .worksheet(sheet)
44            .ok()
45            .and_then(|ws| {
46                ws.conditional_formatting
47                    .iter()
48                    .find(|cf| cf.priority == priority)
49                    .map(|cf| cf.cf_rule.clone())
50            })
51            .unwrap_or(CfRule::DuplicateValues {
52                dxf_id: 0,
53                stop_if_true: false,
54            });
55        self.push_diff_list(vec![Diff::AddConditionalFormatting {
56            sheet,
57            range: range.to_string(),
58            rule: Box::new(stored_rule),
59            priority,
60        }]);
61        self.evaluate_if_not_paused();
62        Ok(())
63    }
64
65    /// Removes the CF rule at `index` from `sheet`.
66    pub fn delete_conditional_formatting(&mut self, sheet: u32, index: u32) -> Result<(), String> {
67        let old = self
68            .model
69            .delete_conditional_formatting(sheet, index as usize)?;
70        self.push_diff_list(vec![Diff::DeleteConditionalFormatting {
71            sheet,
72            index,
73            old_range: old.range,
74            old_rule: Box::new(old.cf_rule),
75            old_priority: old.priority,
76        }]);
77        self.evaluate_if_not_paused();
78        Ok(())
79    }
80
81    /// Replaces the range and rule of the CF entry at `index` on `sheet`.
82    pub fn update_conditional_formatting(
83        &mut self,
84        sheet: u32,
85        index: u32,
86        new_range: &str,
87        new_rule: CfRuleInput,
88    ) -> Result<(), String> {
89        let old =
90            self.model
91                .update_conditional_formatting(sheet, index as usize, new_range, new_rule)?;
92        // Read back the stored entry so the Diff contains the dxf_id that was assigned.
93        let stored_rule = self
94            .model
95            .workbook
96            .worksheet(sheet)
97            .ok()
98            .and_then(|ws| {
99                ws.conditional_formatting
100                    .get(index as usize)
101                    .map(|cf| cf.cf_rule.clone())
102            })
103            .unwrap_or(CfRule::DuplicateValues {
104                dxf_id: 0,
105                stop_if_true: false,
106            });
107        self.push_diff_list(vec![Diff::UpdateConditionalFormatting {
108            sheet,
109            index,
110            old_range: old.range,
111            old_rule: Box::new(old.cf_rule),
112            old_priority: old.priority,
113            new_range: new_range.to_string(),
114            new_rule: Box::new(stored_rule),
115        }]);
116        self.evaluate_if_not_paused();
117        Ok(())
118    }
119
120    /// Raises the priority of the CF rule at `index` on `sheet` by swapping its
121    /// priority with the next-higher-priority rule. No-op if it is already the
122    /// highest-priority rule.
123    pub fn raise_conditional_formatting_priority(
124        &mut self,
125        sheet: u32,
126        index: u32,
127    ) -> Result<(), String> {
128        self.swap_conditional_formatting_priority(sheet, index, true)
129    }
130
131    /// Lowers the priority of the CF rule at `index` on `sheet` by swapping its
132    /// priority with the next-lower-priority rule. No-op if it is already the
133    /// lowest-priority rule.
134    pub fn lower_conditional_formatting_priority(
135        &mut self,
136        sheet: u32,
137        index: u32,
138    ) -> Result<(), String> {
139        self.swap_conditional_formatting_priority(sheet, index, false)
140    }
141
142    /// Shared implementation for raising/lowering CF priority. Snapshots the
143    /// priorities before and after delegating to the base model, then records a
144    /// `SwapConditionalFormattingPriority` diff for the (at most two) rules whose
145    /// priority changed so the operation can be undone/redone.
146    fn swap_conditional_formatting_priority(
147        &mut self,
148        sheet: u32,
149        index: u32,
150        raise: bool,
151    ) -> Result<(), String> {
152        let before: Vec<u32> = self
153            .model
154            .workbook
155            .worksheet(sheet)?
156            .conditional_formatting
157            .iter()
158            .map(|cf| cf.priority)
159            .collect();
160        if raise {
161            self.model
162                .raise_conditional_formatting_priority(sheet, index as usize)?;
163        } else {
164            self.model
165                .lower_conditional_formatting_priority(sheet, index as usize)?;
166        }
167        let after: Vec<u32> = self
168            .model
169            .workbook
170            .worksheet(sheet)?
171            .conditional_formatting
172            .iter()
173            .map(|cf| cf.priority)
174            .collect();
175        // A successful swap touches exactly two rules; if nothing changed (the
176        // rule was already at the boundary) there is nothing to record.
177        let changed: Vec<usize> = (0..before.len())
178            .filter(|&i| before[i] != after[i])
179            .collect();
180        if let [a, b] = changed[..] {
181            self.push_diff_list(vec![Diff::SwapConditionalFormattingPriority {
182                sheet,
183                index_a: a as u32,
184                index_b: b as u32,
185                priority_a: before[a],
186                priority_b: before[b],
187            }]);
188        }
189        self.evaluate_if_not_paused();
190        Ok(())
191    }
192}