ironcalc_base/user_model/
merged_cells.rs1use crate::expressions::types::Area;
2use crate::merged_cells::{merge_across_ranges, merge_down_ranges};
3use crate::types::{Cell, MergedCell};
4use std::borrow::Cow;
5
6use super::{common::UserModel, history::Diff};
7
8impl UserModel<'_> {
9 pub fn merge_cells(&mut self, range: &Area) -> Result<(), String> {
22 self.merge_ranges(std::slice::from_ref(range), false)
23 }
24
25 pub fn merge_cells_center(&mut self, range: &Area) -> Result<(), String> {
32 self.merge_ranges(std::slice::from_ref(range), true)
33 }
34
35 pub fn merge_cells_across(&mut self, range: &Area) -> Result<(), String> {
43 self.merge_ranges(&merge_across_ranges(range)?, false)
44 }
45
46 pub fn merge_cells_down(&mut self, range: &Area) -> Result<(), String> {
54 self.merge_ranges(&merge_down_ranges(range)?, false)
55 }
56
57 fn merge_ranges(&mut self, ranges: &[Area], center: bool) -> Result<(), String> {
60 let Some(first) = ranges.first() else {
61 return Ok(());
62 };
63 let sheet = first.sheet;
64 for range in ranges {
67 self.model.check_merge(range)?;
68 }
69 let old_merged_cells = self.model.get_merged_cells(sheet)?.to_vec();
70
71 let mut diff_list = Vec::new();
72 for range in ranges {
73 self.merge_range_collect_diffs(range, center, &mut diff_list)?;
74 }
75 self.snap_selection_to_merged_area(ranges);
76
77 let new_merged_cells = self.model.get_merged_cells(sheet)?.to_vec();
78 diff_list.push(Diff::SetMergedCells {
79 sheet,
80 old_value: old_merged_cells,
81 new_value: new_merged_cells,
82 });
83 self.push_diff_list(diff_list);
84 self.evaluate_if_not_paused();
85 Ok(())
86 }
87
88 fn merge_range_collect_diffs(
92 &mut self,
93 range: &Area,
94 center: bool,
95 diff_list: &mut Vec<Diff>,
96 ) -> Result<(), String> {
97 let sheet = range.sheet;
98 diff_list.extend(self.covered_cells_clear_diffs(range)?);
99
100 let worksheet = self.model.workbook.worksheet(sheet)?;
105 let anchor_old_value = worksheet.cell(range.row, range.column).map(Cow::into_owned);
106 let anchor_old_link = worksheet.links.get(&(range.row, range.column)).cloned();
107
108 let mut old_styles = Vec::new();
111 for row in range.row..range.row + range.height {
112 for column in range.column..range.column + range.width {
113 old_styles.push((
114 row,
115 column,
116 self.model.get_cell_style_or_none(sheet, row, column)?,
117 ));
118 }
119 }
120
121 self.model.merge_cells(range)?;
122 if center {
123 self.model.center_merged_range(range)?;
124 }
125
126 let worksheet = self.model.workbook.worksheet(sheet)?;
127 let anchor_new_value = worksheet.cell(range.row, range.column).map(Cow::into_owned);
128 let anchor_had_content = !matches!(&anchor_old_value, None | Some(Cell::EmptyCell { .. }));
129 let anchor_has_content = !matches!(&anchor_new_value, None | Some(Cell::EmptyCell { .. }));
130 if !anchor_had_content && anchor_has_content {
131 let new_value =
132 self.model
133 .get_localized_cell_content(sheet, range.row, range.column)?;
134 diff_list.push(Diff::SetCellValue {
135 sheet,
136 row: range.row,
137 column: range.column,
138 new_value,
139 old_value: Box::new(anchor_old_value),
140 });
141 }
142 let anchor_new_link = worksheet.links.get(&(range.row, range.column)).cloned();
143 if anchor_new_link != anchor_old_link {
144 diff_list.push(Diff::SetCellLink {
145 sheet,
146 row: range.row,
147 column: range.column,
148 old_value: Box::new(anchor_old_link),
149 new_value: Box::new(anchor_new_link),
150 });
151 }
152
153 for (row, column, old_style) in old_styles {
154 let new_style = self.model.get_cell_style_or_none(sheet, row, column)?;
155 if new_style != old_style {
156 if let Some(new_style) = new_style {
157 diff_list.push(Diff::SetCellStyle {
158 sheet,
159 row,
160 column,
161 old_value: Box::new(old_style),
162 new_value: Box::new(new_style),
163 });
164 }
165 }
166 }
167 Ok(())
168 }
169
170 fn snap_selection_to_merged_area(&mut self, ranges: &[Area]) {
177 let Some(first) = ranges.first() else {
178 return;
179 };
180 let row = ranges.iter().map(|r| r.row).min().unwrap_or(first.row);
181 let column = ranges
182 .iter()
183 .map(|r| r.column)
184 .min()
185 .unwrap_or(first.column);
186 let last_row = ranges
187 .iter()
188 .map(|r| r.row + r.height - 1)
189 .max()
190 .unwrap_or(first.row);
191 let last_column = ranges
192 .iter()
193 .map(|r| r.column + r.width - 1)
194 .max()
195 .unwrap_or(first.column);
196 if let Ok(worksheet) = self.model.workbook.worksheet_mut(first.sheet) {
197 if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
198 if view.row >= row
199 && view.row <= last_row
200 && view.column >= column
201 && view.column <= last_column
202 {
203 view.row = row;
204 view.column = column;
205 view.range = [row, column, last_row, last_column];
206 view.focus_row = last_row;
207 view.focus_column = last_column;
208 }
209 }
210 }
211 }
212
213 pub fn unmerge_cells(&mut self, range: &Area) -> Result<(), String> {
219 let sheet = range.sheet;
220 let old_value = self.model.get_merged_cells(sheet)?.to_vec();
221 self.model.unmerge_cells(range)?;
222 let new_value = self.model.get_merged_cells(sheet)?.to_vec();
223 if old_value == new_value {
224 return Ok(());
226 }
227 self.push_diff_list(vec![Diff::SetMergedCells {
228 sheet,
229 old_value,
230 new_value,
231 }]);
232 self.evaluate_if_not_paused();
233 Ok(())
234 }
235
236 pub fn get_merged_cells(&self, sheet: u32) -> Result<Vec<MergedCell>, String> {
238 Ok(self.model.get_merged_cells(sheet)?.to_vec())
239 }
240
241 pub(super) fn covered_cells_clear_diffs(&self, range: &Area) -> Result<Vec<Diff>, String> {
244 let sheet = range.sheet;
245 let mut diff_list: Vec<Diff> = Vec::new();
246 let worksheet = self.model.workbook.worksheet(sheet)?;
247 for row in range.row..range.row + range.height {
248 for column in range.column..range.column + range.width {
249 if row == range.row && column == range.column {
250 continue;
251 }
252 if let Some(link) = worksheet.links.get(&(row, column)) {
253 diff_list.push(Diff::SetCellLink {
254 sheet,
255 row,
256 column,
257 old_value: Box::new(Some(link.clone())),
258 new_value: Box::new(None),
259 });
260 }
261 let old_value = worksheet.cell(row, column).map(Cow::into_owned);
262 if matches!(&old_value, None | Some(Cell::EmptyCell { .. })) {
263 continue;
264 }
265 diff_list.push(Diff::SetCellValue {
266 sheet,
267 row,
268 column,
269 new_value: "".to_string(),
270 old_value: Box::new(old_value),
271 });
272 }
273 }
274 Ok(diff_list)
275 }
276}