1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::expressions::parser::stringify::DisplaceData;
use crate::model::Model;
// NOTE: There is a difference with Excel behaviour when deleting cells/rows/columns
// In Excel if the whole range is deleted then it will substitute for #REF!
// In IronCalc, if one of the edges of the range is deleted will replace the edge with #REF!
// I feel this is unimportant for now.
impl Model {
/// This function iterates over all cells in the model and shifts their formulas according to the displacement data.
///
/// # Arguments
///
/// * `displace_data` - A reference to `DisplaceData` describing the displacement's direction and magnitude.
fn displace_cells(&mut self, displace_data: &DisplaceData) {
let cells = self.get_all_cells();
for cell in cells {
self.shift_cell_formula(cell.index, cell.row, cell.column, displace_data);
}
}
/// Retrieves the column indices for a specific row in a given sheet, sorted in ascending or descending order.
///
/// # Arguments
///
/// * `sheet` - The sheet number to retrieve columns from.
/// * `row` - The row number to retrieve columns for.
/// * `descending` - If true, the columns are returned in descending order; otherwise, in ascending order.
///
/// # Returns
///
/// This function returns a `Result` containing either:
/// - `Ok(Vec<i32>)`: A vector of column indices for the specified row, sorted according to the `descending` flag.
/// - `Err(String)`: An error message if the sheet cannot be found.
fn get_columns_for_row(
&self,
sheet: u32,
row: i32,
descending: bool,
) -> Result<Vec<i32>, String> {
let worksheet = self.workbook.worksheet(sheet)?;
if let Some(row_data) = worksheet.sheet_data.get(&row) {
let mut columns: Vec<i32> = row_data.keys().copied().collect();
columns.sort_unstable();
if descending {
columns.reverse();
}
Ok(columns)
} else {
Ok(vec![])
}
}
/// Moves the contents of cell (source_row, source_column) to (target_row, target_column).
///
/// # Arguments
///
/// * `sheet` - The sheet number to retrieve columns from.
/// * `source_row` - The row index of the cell's current location.
/// * `source_column` - The column index of the cell's current location.
/// * `target_row` - The row index of the cell's new location.
/// * `target_column` - The column index of the cell's new location.
fn move_cell(
&mut self,
sheet: u32,
source_row: i32,
source_column: i32,
target_row: i32,
target_column: i32,
) -> Result<(), String> {
let source_cell = self
.workbook
.worksheet(sheet)?
.cell(source_row, source_column)
.ok_or("Expected Cell to exist")?;
let style = source_cell.get_style();
// FIXME: we need some user_input getter instead of get_text
let formula_or_value = self
.cell_formula(sheet, source_row, source_column)?
.unwrap_or_else(|| source_cell.get_text(&self.workbook.shared_strings, &self.language));
self.set_user_input(sheet, target_row, target_column, formula_or_value);
self.workbook
.worksheet_mut(sheet)?
.set_cell_style(target_row, target_column, style);
self.delete_cell(sheet, source_row, source_column)?;
Ok(())
}
/// Inserts one or more new columns into the model at the specified index.
///
/// This method shifts existing columns to the right to make space for the new columns.
///
/// # Arguments
///
/// * `sheet` - The sheet number to retrieve columns from.
/// * `column` - The index at which the new columns should be inserted.
/// * `column_count` - The number of columns to insert.
pub fn insert_columns(
&mut self,
sheet: u32,
column: i32,
column_count: i32,
) -> Result<(), String> {
if column_count <= 0 {
return Err("Cannot add a negative number of cells :)".to_string());
}
// check if it is possible:
let dimensions = self.workbook.worksheet(sheet)?.dimension();
let last_column = dimensions.max_column + column_count;
if last_column > LAST_COLUMN {
return Err(
"Cannot shift cells because that would delete cells at the end of a row"
.to_string(),
);
}
let worksheet = self.workbook.worksheet(sheet)?;
let all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
for row in all_rows {
let sorted_columns = self.get_columns_for_row(sheet, row, true)?;
for col in sorted_columns {
if col >= column {
self.move_cell(sheet, row, col, row, col + column_count)?;
} else {
// Break because columns are in descending order.
break;
}
}
}
// Update all formulas in the workbook
self.displace_cells(
&(DisplaceData::Column {
sheet,
column,
delta: column_count,
}),
);
Ok(())
}
/// Deletes one or more columns from the model starting at the specified index.
///
/// # Arguments
///
/// * `sheet` - The sheet number to retrieve columns from.
/// * `column` - The index of the first column to delete.
/// * `count` - The number of columns to delete.
pub fn delete_columns(
&mut self,
sheet: u32,
column: i32,
column_count: i32,
) -> Result<(), String> {
if column_count <= 0 {
return Err("Please use insert columns instead".to_string());
}
// Move cells
let worksheet = &self.workbook.worksheet(sheet)?;
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
// We do not need to do that, but it is safer to eliminate sources of randomness in the algorithm
all_rows.sort_unstable();
for r in all_rows {
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
for col in columns {
if col >= column {
if col >= column + column_count {
self.move_cell(sheet, r, col, r, col - column_count)?;
} else {
self.delete_cell(sheet, r, col)?;
}
}
}
}
// Update all formulas in the workbook
self.displace_cells(
&(DisplaceData::Column {
sheet,
column,
delta: -column_count,
}),
);
Ok(())
}
/// Inserts one or more new rows into the model at the specified index.
///
/// # Arguments
///
/// * `sheet` - The sheet number to retrieve columns from.
/// * `row` - The index at which the new rows should be inserted.
/// * `row_count` - The number of rows to insert.
pub fn insert_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> {
if row_count <= 0 {
return Err("Cannot add a negative number of cells :)".to_string());
}
// Check if it is possible:
let dimensions = self.workbook.worksheet(sheet)?.dimension();
let last_row = dimensions.max_row + row_count;
if last_row > LAST_ROW {
return Err(
"Cannot shift cells because that would delete cells at the end of a column"
.to_string(),
);
}
// Move cells
let worksheet = &self.workbook.worksheet(sheet)?;
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
all_rows.sort_unstable();
all_rows.reverse();
for r in all_rows {
if r >= row {
// We do not really need the columns in any order
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
for column in columns {
self.move_cell(sheet, r, column, r + row_count, column)?;
}
} else {
// Rows are in descending order
break;
}
}
// In the list of rows styles:
// * Add all rows above the rows we are inserting unchanged
// * Shift the ones below
let rows = &self.workbook.worksheets[sheet as usize].rows;
let mut new_rows = vec![];
for r in rows {
if r.r < row {
new_rows.push(r.clone());
} else if r.r >= row {
let mut new_row = r.clone();
new_row.r = r.r + row_count;
new_rows.push(new_row);
}
}
self.workbook.worksheets[sheet as usize].rows = new_rows;
// Update all formulas in the workbook
self.displace_cells(
&(DisplaceData::Row {
sheet,
row,
delta: row_count,
}),
);
Ok(())
}
/// Deletes one or more rows from the model starting at the specified index.
///
/// # Arguments
///
/// * `sheet` - The sheet number to retrieve columns from.
/// * `row` - The index of the first row to delete.
/// * `row_count` - The number of rows to delete.
pub fn delete_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> {
if row_count <= 0 {
return Err("Please use insert rows instead".to_string());
}
// Move cells
let worksheet = &self.workbook.worksheet(sheet)?;
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
all_rows.sort_unstable();
for r in all_rows {
if r >= row {
// We do not need ordered, but it is safer to eliminate sources of randomness in the algorithm
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
if r >= row + row_count {
// displace all cells in column
for column in columns {
self.move_cell(sheet, r, column, r - row_count, column)?;
}
} else {
// remove all cells in row
// FIXME: We could just remove the entire row in one go
for column in columns {
self.delete_cell(sheet, r, column)?;
}
}
}
}
// In the list of rows styles:
// * Add all rows above the rows we are deleting unchanged
// * Skip all those we are deleting
// * Shift the ones below
let rows = &self.workbook.worksheets[sheet as usize].rows;
let mut new_rows = vec![];
for r in rows {
if r.r < row {
new_rows.push(r.clone());
} else if r.r >= row + row_count {
let mut new_row = r.clone();
new_row.r = r.r - row_count;
new_rows.push(new_row);
}
}
self.workbook.worksheets[sheet as usize].rows = new_rows;
self.displace_cells(
&(DisplaceData::Row {
sheet,
row,
delta: -row_count,
}),
);
Ok(())
}
/// Displaces cells due to a move column action
/// from initial_column to target_column = initial_column + column_delta
/// References will be updated following:
/// Cell references:
/// * All cell references to initial_column will go to target_column
/// * All cell references to columns in between (initial_column, target_column] will be displaced one to the left
/// * All other cell references are left unchanged
/// Ranges. This is the tricky bit:
/// * Column is one of the extremes of the range. The new extreme would be target_column.
/// Range is then normalized
/// * Any other case, range is left unchanged.
/// NOTE: This does NOT move the data in the columns or move the colum styles
pub fn move_column_action(
&mut self,
sheet: u32,
column: i32,
delta: i32,
) -> Result<(), &'static str> {
// Check boundaries
let target_column = column + delta;
if !(1..=LAST_COLUMN).contains(&target_column) {
return Err("Target column out of boundaries");
}
if !(1..=LAST_COLUMN).contains(&column) {
return Err("Initial column out of boundaries");
}
// TODO: Add the actual displacement of data and styles
// Update all formulas in the workbook
self.displace_cells(
&(DisplaceData::ColumnMove {
sheet,
column,
delta,
}),
);
Ok(())
}
}