UPDATE: Add rows/column style APIs
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
7e54cb6aa2
commit
23ab5dfef2
@@ -1872,12 +1872,29 @@ impl Model {
|
||||
}
|
||||
|
||||
/// Returns the style for cell (`sheet`, `row`, `column`)
|
||||
/// If the cell does not have a style defined we check the row, otherwise the column and finally a default
|
||||
pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Result<Style, String> {
|
||||
let style_index = self.get_cell_style_index(sheet, row, column)?;
|
||||
let style = self.workbook.styles.get_style(style_index)?;
|
||||
Ok(style)
|
||||
}
|
||||
|
||||
/// Returns the style defined in a cell if any.
|
||||
pub fn get_cell_style_or_none(
|
||||
&self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
) -> Result<Option<Style>, String> {
|
||||
let style = self
|
||||
.workbook
|
||||
.worksheet(sheet)?
|
||||
.cell(row, column)
|
||||
.map(|c| self.workbook.styles.get_style(c.get_style()))
|
||||
.transpose();
|
||||
style
|
||||
}
|
||||
|
||||
/// Returns an internal binary representation of the workbook
|
||||
///
|
||||
/// See also:
|
||||
@@ -2161,6 +2178,73 @@ impl Model {
|
||||
Err("Defined name not found".to_string())
|
||||
}
|
||||
}
|
||||
/// Returns the style object of a column, if any
|
||||
pub fn get_column_style(&self, sheet: u32, column: i32) -> Result<Option<Style>, String> {
|
||||
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
||||
let cols = &worksheet.cols;
|
||||
for col in cols {
|
||||
if column >= col.min && column <= col.max {
|
||||
if let Some(style_index) = col.style {
|
||||
let style = self.workbook.styles.get_style(style_index)?;
|
||||
return Ok(Some(style));
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
} else {
|
||||
Err("Invalid sheet".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the style object of a row, if any
|
||||
pub fn get_row_style(&self, sheet: u32, row: i32) -> Result<Option<Style>, String> {
|
||||
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
||||
let rows = &worksheet.rows;
|
||||
for r in rows {
|
||||
if row == r.r {
|
||||
let style = self.workbook.styles.get_style(r.s)?;
|
||||
return Ok(Some(style));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
} else {
|
||||
Err("Invalid sheet".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a column with style
|
||||
pub fn set_column_style(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
column: i32,
|
||||
style: &Style,
|
||||
) -> Result<(), String> {
|
||||
let style_index = self.workbook.styles.get_style_index_or_create(style);
|
||||
self.workbook
|
||||
.worksheet_mut(sheet)?
|
||||
.set_column_style(column, style_index)
|
||||
}
|
||||
|
||||
/// Sets a row with style
|
||||
pub fn set_row_style(&mut self, sheet: u32, row: i32, style: &Style) -> Result<(), String> {
|
||||
let style_index = self.workbook.styles.get_style_index_or_create(style);
|
||||
self.workbook
|
||||
.worksheet_mut(sheet)?
|
||||
.set_row_style(row, style_index)
|
||||
}
|
||||
|
||||
/// Deletes the style of a column if the is any
|
||||
pub fn delete_column_style(&mut self, sheet: u32, column: i32) -> Result<(), String> {
|
||||
self.workbook
|
||||
.worksheet_mut(sheet)?
|
||||
.delete_column_style(column)
|
||||
}
|
||||
|
||||
/// Deletes the style of a row if there is any
|
||||
pub fn delete_row_style(&mut self, sheet: u32, row: i32) -> Result<(), String> {
|
||||
self.workbook.worksheet_mut(sheet)?.delete_row_style(row)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -301,7 +301,7 @@ impl Model {
|
||||
};
|
||||
if sheet_index >= sheet_count {
|
||||
return Err("Sheet index too large".to_string());
|
||||
}
|
||||
};
|
||||
self.workbook.worksheets.remove(sheet_index as usize);
|
||||
self.reset_parsed_structures();
|
||||
Ok(())
|
||||
|
||||
@@ -4,8 +4,6 @@ use crate::{
|
||||
types::{Border, CellStyles, CellXfs, Fill, Font, NumFmt, Style, Styles},
|
||||
};
|
||||
|
||||
// TODO: Move Styles and all related types from crate::types here
|
||||
// Not doing it right now to not have conflicts with exporter branch
|
||||
impl Styles {
|
||||
fn get_font_index(&self, font: &Font) -> Option<i32> {
|
||||
for (font_index, item) in self.fonts.iter().enumerate() {
|
||||
|
||||
@@ -37,6 +37,7 @@ mod test_model_cell_clear_all;
|
||||
mod test_model_is_empty_cell;
|
||||
mod test_move_formula;
|
||||
mod test_quote_prefix;
|
||||
mod test_row_column_styles;
|
||||
mod test_set_user_input;
|
||||
mod test_sheet_markup;
|
||||
mod test_sheets;
|
||||
|
||||
32
base/src/test/test_row_column_styles.rs
Normal file
32
base/src/test/test_row_column_styles.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{constants::DEFAULT_COLUMN_WIDTH, test::util::new_empty_model};
|
||||
|
||||
#[test]
|
||||
fn test_model_set_cells_with_values_styles() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
let style_base = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
let mut style = style_base.clone();
|
||||
style.font.b = true;
|
||||
|
||||
model.set_column_style(0, 10, &style).unwrap();
|
||||
|
||||
assert!(model.get_style_for_cell(0, 21, 10).unwrap().font.b);
|
||||
|
||||
model.delete_column_style(0, 10).unwrap();
|
||||
|
||||
// There are no styles in the column
|
||||
assert!(model.workbook.worksheets[0].cols.is_empty());
|
||||
|
||||
// lets change the column width and check it does not affect the style
|
||||
model
|
||||
.set_column_width(0, 10, DEFAULT_COLUMN_WIDTH * 2.0)
|
||||
.unwrap();
|
||||
model.set_column_style(0, 10, &style).unwrap();
|
||||
|
||||
model.delete_column_style(0, 10).unwrap();
|
||||
|
||||
// There are no styles in the column
|
||||
assert!(model.workbook.worksheets[0].cols.len() == 1);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ mod test_autofill_columns;
|
||||
mod test_autofill_rows;
|
||||
mod test_border;
|
||||
mod test_clear_cells;
|
||||
mod test_column_style;
|
||||
mod test_defined_names;
|
||||
mod test_diff_queue;
|
||||
mod test_evaluation;
|
||||
|
||||
332
base/src/test/user_model/test_column_style.rs
Normal file
332
base/src/test/user_model/test_column_style.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT, LAST_COLUMN, LAST_ROW};
|
||||
use crate::expressions::types::Area;
|
||||
use crate::UserModel;
|
||||
|
||||
#[test]
|
||||
fn column_width() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
||||
assert!(!style.font.i);
|
||||
assert!(!style.font.b);
|
||||
assert!(!style.font.u);
|
||||
assert!(!style.font.strike);
|
||||
assert_eq!(style.font.color, Some("#000000".to_owned()));
|
||||
|
||||
// Set the whole column style and check it works
|
||||
model.update_range_style(&range, "font.b", "true").unwrap();
|
||||
let style = model.get_cell_style(0, 109, 7).unwrap();
|
||||
assert!(style.font.b);
|
||||
|
||||
// undo and check it works
|
||||
model.undo().unwrap();
|
||||
let style = model.get_cell_style(0, 109, 7).unwrap();
|
||||
assert!(!style.font.b);
|
||||
|
||||
// redo and check it works
|
||||
model.redo().unwrap();
|
||||
let style = model.get_cell_style(0, 109, 7).unwrap();
|
||||
assert!(style.font.b);
|
||||
|
||||
// change the column width and check it does not affect the style
|
||||
model
|
||||
.set_column_width(0, 7, DEFAULT_COLUMN_WIDTH * 2.0)
|
||||
.unwrap();
|
||||
let style = model.get_cell_style(0, 109, 7).unwrap();
|
||||
assert!(style.font.b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn existing_style() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let cell_g123 = Area {
|
||||
sheet: 0,
|
||||
row: 123,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
// Set G123 background to red
|
||||
model
|
||||
.update_range_style(&cell_g123, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
// Now set the style of the whole column
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
// Get the style of G123
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
// Check the style of G123 is now what it was before
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
|
||||
|
||||
model.redo().unwrap();
|
||||
|
||||
// Check G123 has the column style now
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_column() {
|
||||
// We set the row style, then a column style
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let row_3_range = Area {
|
||||
sheet: 0,
|
||||
row: 3,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// update the row style
|
||||
model
|
||||
.update_range_style(&row_3_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
// update the column style
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
// Check G3 has the column style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
|
||||
|
||||
// undo twice. Color must be default
|
||||
model.undo().unwrap();
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
|
||||
model.undo().unwrap();
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_row() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let default_style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let row_3_range = Area {
|
||||
sheet: 0,
|
||||
row: 3,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// update the column style
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
// update the row style
|
||||
model
|
||||
.update_range_style(&row_3_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
// Check G3 has the row style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
// Check G3 has the column style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
// Check G3 has the default_style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, default_style.fill.bg_color);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_column_column() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let column_c_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 3,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let column_e_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 5,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let row_5_range = Area {
|
||||
sheet: 0,
|
||||
row: 5,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// update the row style
|
||||
model
|
||||
.update_range_style(&row_5_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
// update the column style
|
||||
model
|
||||
.update_range_style(&column_c_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
model
|
||||
.update_range_style(&column_e_range, "fill.bg_color", "#CCC111")
|
||||
.unwrap();
|
||||
|
||||
model.undo().unwrap();
|
||||
model.undo().unwrap();
|
||||
model.undo().unwrap();
|
||||
|
||||
// Test E5 has the default style
|
||||
let style = model.get_cell_style(0, 5, 5).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn width_column_undo() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
model
|
||||
.set_column_width(0, 7, DEFAULT_COLUMN_WIDTH * 2.0)
|
||||
.unwrap();
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#CCC111")
|
||||
.unwrap();
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
model.get_column_width(0, 7).unwrap(),
|
||||
DEFAULT_COLUMN_WIDTH * 2.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn height_row_undo() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model
|
||||
.set_row_height(0, 10, DEFAULT_ROW_HEIGHT * 2.0)
|
||||
.unwrap();
|
||||
|
||||
let row_10_range = Area {
|
||||
sheet: 0,
|
||||
row: 10,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
model
|
||||
.update_range_style(&row_10_range, "fill.bg_color", "#CCC111")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
model.get_row_height(0, 10).unwrap(),
|
||||
2.0 * DEFAULT_ROW_HEIGHT
|
||||
);
|
||||
model.undo().unwrap();
|
||||
assert_eq!(
|
||||
model.get_row_height(0, 10).unwrap(),
|
||||
2.0 * DEFAULT_ROW_HEIGHT
|
||||
);
|
||||
model.undo().unwrap();
|
||||
assert_eq!(model.get_row_height(0, 10).unwrap(), DEFAULT_ROW_HEIGHT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cell_row_undo() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let cell_g12 = Area {
|
||||
sheet: 0,
|
||||
row: 12,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
let row_12_range = Area {
|
||||
sheet: 0,
|
||||
row: 12,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// Set G12 background to red
|
||||
model
|
||||
.update_range_style(&cell_g12, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
model
|
||||
.update_range_style(&row_12_range, "fill.bg_color", "#CCC111")
|
||||
.unwrap();
|
||||
|
||||
let style = model.get_cell_style(0, 12, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#CCC111".to_string()));
|
||||
model.undo().unwrap();
|
||||
|
||||
let style = model.get_cell_style(0, 12, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_string()));
|
||||
}
|
||||
@@ -312,7 +312,7 @@ impl Default for Styles {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
|
||||
pub struct Style {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub alignment: Option<Alignment>,
|
||||
|
||||
@@ -109,6 +109,78 @@ fn vertical(value: &str) -> Result<VerticalAlignment, String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_style(old_value: &Style, style_path: &str, value: &str) -> Result<Style, String> {
|
||||
let mut style = old_value.clone();
|
||||
match style_path {
|
||||
"font.b" => {
|
||||
style.font.b = boolean(value)?;
|
||||
}
|
||||
"font.i" => {
|
||||
style.font.i = boolean(value)?;
|
||||
}
|
||||
"font.u" => {
|
||||
style.font.u = boolean(value)?;
|
||||
}
|
||||
"font.strike" => {
|
||||
style.font.strike = boolean(value)?;
|
||||
}
|
||||
"font.color" => {
|
||||
style.font.color = color(value)?;
|
||||
}
|
||||
"fill.bg_color" => {
|
||||
style.fill.bg_color = color(value)?;
|
||||
style.fill.pattern_type = "solid".to_string();
|
||||
}
|
||||
"fill.fg_color" => {
|
||||
style.fill.fg_color = color(value)?;
|
||||
style.fill.pattern_type = "solid".to_string();
|
||||
}
|
||||
"num_fmt" => {
|
||||
value.clone_into(&mut style.num_fmt);
|
||||
}
|
||||
"alignment" => {
|
||||
if !value.is_empty() {
|
||||
return Err(format!("Alignment must be empty, but found: '{value}'."));
|
||||
}
|
||||
style.alignment = None;
|
||||
}
|
||||
"alignment.horizontal" => match style.alignment {
|
||||
Some(ref mut s) => s.horizontal = horizontal(value)?,
|
||||
None => {
|
||||
let alignment = Alignment {
|
||||
horizontal: horizontal(value)?,
|
||||
..Default::default()
|
||||
};
|
||||
style.alignment = Some(alignment)
|
||||
}
|
||||
},
|
||||
"alignment.vertical" => match style.alignment {
|
||||
Some(ref mut s) => s.vertical = vertical(value)?,
|
||||
None => {
|
||||
let alignment = Alignment {
|
||||
vertical: vertical(value)?,
|
||||
..Default::default()
|
||||
};
|
||||
style.alignment = Some(alignment)
|
||||
}
|
||||
},
|
||||
"alignment.wrap_text" => match style.alignment {
|
||||
Some(ref mut s) => s.wrap_text = boolean(value)?,
|
||||
None => {
|
||||
let alignment = Alignment {
|
||||
wrap_text: boolean(value)?,
|
||||
..Default::default()
|
||||
};
|
||||
style.alignment = Some(alignment)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(format!("Invalid style path: '{style_path}'."));
|
||||
}
|
||||
}
|
||||
Ok(style)
|
||||
}
|
||||
|
||||
/// # A wrapper around [`Model`] for a spreadsheet end user.
|
||||
/// UserModel is a wrapper around Model with undo/redo history, _diffs_, automatic evaluation and view management.
|
||||
///
|
||||
@@ -821,7 +893,7 @@ impl UserModel {
|
||||
let row_index = ((row - row_start) % styles_height) as usize;
|
||||
let column_index = ((column - column_start) % styles_width) as usize;
|
||||
let style = &styles[row_index][column_index];
|
||||
let old_value = self.model.get_style_for_cell(sheet, row, column)?;
|
||||
let old_value = self.model.get_cell_style_or_none(sheet, row, column)?;
|
||||
self.model.set_cell_style(sheet, row, column, style)?;
|
||||
diff_list.push(Diff::SetCellStyle {
|
||||
sheet,
|
||||
@@ -922,7 +994,7 @@ impl UserModel {
|
||||
sheet,
|
||||
row: row - 1,
|
||||
column,
|
||||
old_value: Box::new(old_value_cell_above),
|
||||
old_value: Box::new(Some(old_value_cell_above)),
|
||||
new_value: Box::new(new_value_cell_above),
|
||||
});
|
||||
}
|
||||
@@ -955,7 +1027,7 @@ impl UserModel {
|
||||
sheet,
|
||||
row,
|
||||
column: column + 1,
|
||||
old_value: Box::new(old_value_cell_right),
|
||||
old_value: Box::new(Some(old_value_cell_right)),
|
||||
new_value: Box::new(new_value_cell_right),
|
||||
});
|
||||
}
|
||||
@@ -988,7 +1060,7 @@ impl UserModel {
|
||||
sheet,
|
||||
row: row + 1,
|
||||
column,
|
||||
old_value: Box::new(old_value_cell_below),
|
||||
old_value: Box::new(Some(old_value_cell_below)),
|
||||
new_value: Box::new(new_value_cell_below),
|
||||
});
|
||||
}
|
||||
@@ -1021,7 +1093,7 @@ impl UserModel {
|
||||
sheet,
|
||||
row,
|
||||
column: column - 1,
|
||||
old_value: Box::new(old_value_cell_left),
|
||||
old_value: Box::new(Some(old_value_cell_left)),
|
||||
new_value: Box::new(new_value_cell_left),
|
||||
});
|
||||
}
|
||||
@@ -1056,7 +1128,7 @@ impl UserModel {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(old_value),
|
||||
old_value: Box::new(Some(old_value)),
|
||||
new_value: Box::new(new_value),
|
||||
});
|
||||
}
|
||||
@@ -1081,7 +1153,7 @@ impl UserModel {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(old_value),
|
||||
old_value: Box::new(Some(old_value)),
|
||||
new_value: Box::new(style),
|
||||
});
|
||||
}
|
||||
@@ -1107,7 +1179,7 @@ impl UserModel {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(old_value),
|
||||
old_value: Box::new(Some(old_value)),
|
||||
new_value: Box::new(style),
|
||||
});
|
||||
}
|
||||
@@ -1132,7 +1204,7 @@ impl UserModel {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(old_value),
|
||||
old_value: Box::new(Some(old_value)),
|
||||
new_value: Box::new(style),
|
||||
});
|
||||
}
|
||||
@@ -1157,7 +1229,7 @@ impl UserModel {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(old_value),
|
||||
old_value: Box::new(Some(old_value)),
|
||||
new_value: Box::new(style),
|
||||
});
|
||||
}
|
||||
@@ -1168,6 +1240,32 @@ impl UserModel {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_single_cell_style(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
style_path: &str,
|
||||
value: &str,
|
||||
diff_list: &mut Vec<Diff>,
|
||||
) -> Result<(), String> {
|
||||
let old_value = self.model.get_cell_style_or_none(sheet, row, column)?;
|
||||
let style = match old_value.as_ref() {
|
||||
Some(s) => s,
|
||||
None => &Style::default(),
|
||||
};
|
||||
let new_style = update_style(style, style_path, value)?;
|
||||
self.model.set_cell_style(sheet, row, column, &new_style)?;
|
||||
diff_list.push(Diff::SetCellStyle {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(old_value),
|
||||
new_value: Box::new(new_style),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the range with a cell style.
|
||||
/// See also:
|
||||
/// * [Model::set_cell_style]
|
||||
@@ -1179,85 +1277,117 @@ impl UserModel {
|
||||
) -> Result<(), String> {
|
||||
let sheet = range.sheet;
|
||||
let mut diff_list = Vec::new();
|
||||
for row in range.row..range.row + range.height {
|
||||
// We need all the rows in the column to update the style
|
||||
// NB: This is too much, this is all the rows that have values
|
||||
let data_rows: Vec<i32> = self
|
||||
.model
|
||||
.workbook
|
||||
.worksheet(sheet)?
|
||||
.sheet_data
|
||||
.keys()
|
||||
.copied()
|
||||
.collect();
|
||||
let styled_rows = &self.model.workbook.worksheet(sheet)?.rows.clone();
|
||||
if range.row == 1 && range.height == LAST_ROW {
|
||||
// Full columns
|
||||
for column in range.column..range.column + range.width {
|
||||
let old_value = self.model.get_style_for_cell(sheet, row, column)?;
|
||||
let mut style = old_value.clone();
|
||||
match style_path {
|
||||
"font.b" => {
|
||||
style.font.b = boolean(value)?;
|
||||
}
|
||||
"font.i" => {
|
||||
style.font.i = boolean(value)?;
|
||||
}
|
||||
"font.u" => {
|
||||
style.font.u = boolean(value)?;
|
||||
}
|
||||
"font.strike" => {
|
||||
style.font.strike = boolean(value)?;
|
||||
}
|
||||
"font.color" => {
|
||||
style.font.color = color(value)?;
|
||||
}
|
||||
"fill.bg_color" => {
|
||||
style.fill.bg_color = color(value)?;
|
||||
style.fill.pattern_type = "solid".to_string();
|
||||
}
|
||||
"fill.fg_color" => {
|
||||
style.fill.fg_color = color(value)?;
|
||||
style.fill.pattern_type = "solid".to_string();
|
||||
}
|
||||
"num_fmt" => {
|
||||
value.clone_into(&mut style.num_fmt);
|
||||
}
|
||||
"alignment" => {
|
||||
if !value.is_empty() {
|
||||
return Err(format!("Alignment must be empty, but found: '{value}'."));
|
||||
}
|
||||
style.alignment = None;
|
||||
}
|
||||
"alignment.horizontal" => match style.alignment {
|
||||
Some(ref mut s) => s.horizontal = horizontal(value)?,
|
||||
None => {
|
||||
let alignment = Alignment {
|
||||
horizontal: horizontal(value)?,
|
||||
..Default::default()
|
||||
};
|
||||
style.alignment = Some(alignment)
|
||||
}
|
||||
},
|
||||
"alignment.vertical" => match style.alignment {
|
||||
Some(ref mut s) => s.vertical = vertical(value)?,
|
||||
None => {
|
||||
let alignment = Alignment {
|
||||
vertical: vertical(value)?,
|
||||
..Default::default()
|
||||
};
|
||||
style.alignment = Some(alignment)
|
||||
}
|
||||
},
|
||||
"alignment.wrap_text" => match style.alignment {
|
||||
Some(ref mut s) => s.wrap_text = boolean(value)?,
|
||||
None => {
|
||||
let alignment = Alignment {
|
||||
wrap_text: boolean(value)?,
|
||||
..Default::default()
|
||||
};
|
||||
style.alignment = Some(alignment)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(format!("Invalid style path: '{style_path}'."));
|
||||
}
|
||||
}
|
||||
self.model.set_cell_style(sheet, row, column, &style)?;
|
||||
diff_list.push(Diff::SetCellStyle {
|
||||
// we set the style of the full column
|
||||
let old_style = self.model.get_column_style(sheet, column)?;
|
||||
let style = match old_style.as_ref() {
|
||||
Some(s) => s,
|
||||
None => &Style::default(),
|
||||
};
|
||||
let style = update_style(style, style_path, value)?;
|
||||
self.model.set_column_style(sheet, column, &style)?;
|
||||
diff_list.push(Diff::SetColumnStyle {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(old_value),
|
||||
old_value: Box::new(old_style),
|
||||
new_value: Box::new(style),
|
||||
});
|
||||
|
||||
// Update style in all cells that have different styles
|
||||
// FIXME: We need a better way to transverse of cells in a column
|
||||
for &row in &data_rows {
|
||||
if let Some(data_row) =
|
||||
self.model.workbook.worksheet(sheet)?.sheet_data.get(&row)
|
||||
{
|
||||
if data_row.get(&column).is_some() {
|
||||
self.update_single_cell_style(
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
style_path,
|
||||
value,
|
||||
&mut diff_list,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We need to update the styles in all cells that have a row style
|
||||
for row_s in styled_rows.iter() {
|
||||
self.update_single_cell_style(
|
||||
sheet,
|
||||
row_s.r,
|
||||
column,
|
||||
style_path,
|
||||
value,
|
||||
&mut diff_list,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
} else if range.column == 1 && range.width == LAST_COLUMN {
|
||||
// Full rows
|
||||
for row in range.row..range.row + range.height {
|
||||
// First update the style of the row
|
||||
let old_style = self.model.get_row_style(sheet, row)?;
|
||||
let style = match old_style.as_ref() {
|
||||
Some(s) => s,
|
||||
None => &Style::default(),
|
||||
};
|
||||
let style = update_style(style, style_path, value)?;
|
||||
self.model.set_row_style(sheet, row, &style)?;
|
||||
diff_list.push(Diff::SetRowStyle {
|
||||
sheet,
|
||||
row,
|
||||
old_value: Box::new(old_style),
|
||||
new_value: Box::new(style),
|
||||
});
|
||||
|
||||
// Now update style in all cells that are not empty
|
||||
let columns: Vec<i32> = self
|
||||
.model
|
||||
.workbook
|
||||
.worksheet(sheet)?
|
||||
.sheet_data
|
||||
.get(&row)
|
||||
.map(|row_data| row_data.keys().copied().collect())
|
||||
.unwrap_or_default();
|
||||
for column in columns {
|
||||
self.update_single_cell_style(
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
style_path,
|
||||
value,
|
||||
&mut diff_list,
|
||||
)?;
|
||||
}
|
||||
// since rows take precedence over columns we do not need
|
||||
// to update the styles in all cells that have a column style
|
||||
}
|
||||
} else {
|
||||
for row in range.row..range.row + range.height {
|
||||
for column in range.column..range.column + range.width {
|
||||
self.update_single_cell_style(
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
style_path,
|
||||
value,
|
||||
&mut diff_list,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.push_diff_list(diff_list);
|
||||
@@ -1394,7 +1524,7 @@ impl UserModel {
|
||||
.worksheet(sheet)?
|
||||
.cell(row, column)
|
||||
.cloned();
|
||||
let old_style = self.model.get_style_for_cell(sheet, row, column)?;
|
||||
let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
|
||||
|
||||
// compute the new value and set it
|
||||
let source_row = anchor_row + index;
|
||||
@@ -1495,7 +1625,7 @@ impl UserModel {
|
||||
.worksheet(sheet)?
|
||||
.cell(row, column)
|
||||
.cloned();
|
||||
let old_style = self.model.get_style_for_cell(sheet, row, column)?;
|
||||
let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
|
||||
|
||||
// compute the new value and set it
|
||||
let source_column = anchor_column + index;
|
||||
@@ -1570,6 +1700,9 @@ impl UserModel {
|
||||
|
||||
let mut data = HashMap::new();
|
||||
let [row_start, column_start, row_end, column_end] = selected_area.range;
|
||||
let dimension = self.model.workbook.worksheet(sheet)?.dimension();
|
||||
let row_end = row_end.min(dimension.max_row);
|
||||
let column_end = column_end.min(dimension.max_column);
|
||||
for row in row_start..=row_end {
|
||||
let mut data_row = HashMap::new();
|
||||
let mut text_row = Vec::new();
|
||||
@@ -1667,9 +1800,9 @@ impl UserModel {
|
||||
.cell(target_row, target_column)
|
||||
.cloned();
|
||||
|
||||
let old_style = self
|
||||
.model
|
||||
.get_style_for_cell(sheet, target_row, target_column)?;
|
||||
let old_style =
|
||||
self.model
|
||||
.get_cell_style_or_none(sheet, target_row, target_column)?;
|
||||
|
||||
self.model
|
||||
.set_user_input(sheet, target_row, target_column, new_value.clone())?;
|
||||
@@ -1922,9 +2055,15 @@ impl UserModel {
|
||||
column,
|
||||
old_value,
|
||||
new_value: _,
|
||||
} => self
|
||||
.model
|
||||
.set_cell_style(*sheet, *row, *column, old_value)?,
|
||||
} => {
|
||||
if let Some(old_style) = old_value.as_ref() {
|
||||
self.model
|
||||
.set_cell_style(*sheet, *row, *column, old_style)?;
|
||||
} else {
|
||||
// If the cell did not have a style there was nothing on it
|
||||
self.model.cell_clear_all(*sheet, *row, *column)?;
|
||||
}
|
||||
}
|
||||
Diff::InsertRow { sheet, row } => {
|
||||
self.model.delete_rows(*sheet, *row, 1)?;
|
||||
needs_evaluation = true;
|
||||
@@ -2073,6 +2212,29 @@ impl UserModel {
|
||||
|
||||
self.set_selected_sheet(sheet_index)?;
|
||||
}
|
||||
Diff::SetColumnStyle {
|
||||
sheet,
|
||||
column,
|
||||
old_value,
|
||||
new_value: _,
|
||||
} => match old_value.as_ref() {
|
||||
Some(s) => self.model.set_column_style(*sheet, *column, s)?,
|
||||
None => {
|
||||
self.model.delete_column_style(*sheet, *column)?;
|
||||
}
|
||||
},
|
||||
Diff::SetRowStyle {
|
||||
sheet,
|
||||
row,
|
||||
old_value,
|
||||
new_value: _,
|
||||
} => {
|
||||
if let Some(s) = old_value.as_ref() {
|
||||
self.model.set_row_style(*sheet, *row, s)?;
|
||||
} else {
|
||||
self.model.delete_row_style(*sheet, *row)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if needs_evaluation {
|
||||
@@ -2244,6 +2406,22 @@ impl UserModel {
|
||||
.worksheet_mut(*sheet)?
|
||||
.set_cell_style(*row, *column, 0)?;
|
||||
}
|
||||
Diff::SetColumnStyle {
|
||||
sheet,
|
||||
column,
|
||||
old_value: _,
|
||||
new_value,
|
||||
} => {
|
||||
self.model.set_column_style(*sheet, *column, new_value)?;
|
||||
}
|
||||
Diff::SetRowStyle {
|
||||
sheet,
|
||||
row,
|
||||
old_value: _,
|
||||
new_value,
|
||||
} => {
|
||||
self.model.set_row_style(*sheet, *row, new_value)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ pub(crate) enum Diff {
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
old_value: Box<Style>,
|
||||
old_value: Box<Option<Style>>,
|
||||
new_value: Box<Style>,
|
||||
},
|
||||
// Column and Row diffs
|
||||
@@ -65,6 +65,18 @@ pub(crate) enum Diff {
|
||||
new_value: f64,
|
||||
old_value: f64,
|
||||
},
|
||||
SetColumnStyle {
|
||||
sheet: u32,
|
||||
column: i32,
|
||||
old_value: Box<Option<Style>>,
|
||||
new_value: Box<Style>,
|
||||
},
|
||||
SetRowStyle {
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
old_value: Box<Option<Style>>,
|
||||
new_value: Box<Style>,
|
||||
},
|
||||
InsertRow {
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
|
||||
@@ -108,15 +108,17 @@ impl Worksheet {
|
||||
self.cols = vec![Col {
|
||||
min: 1,
|
||||
max: constants::LAST_COLUMN,
|
||||
width: constants::DEFAULT_COLUMN_WIDTH / constants::COLUMN_WIDTH_FACTOR,
|
||||
custom_width: true,
|
||||
width: constants::DEFAULT_COLUMN_WIDTH,
|
||||
custom_width: false,
|
||||
style: Some(style_index),
|
||||
}];
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_column_style(&mut self, column: i32, style_index: i32) -> Result<(), String> {
|
||||
let width = constants::DEFAULT_COLUMN_WIDTH / constants::COLUMN_WIDTH_FACTOR;
|
||||
let width = self
|
||||
.get_column_width(column)
|
||||
.unwrap_or(constants::DEFAULT_COLUMN_WIDTH);
|
||||
self.set_column_width_and_style(column, width, Some(style_index))
|
||||
}
|
||||
|
||||
@@ -139,6 +141,82 @@ impl Worksheet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_row_style(&mut self, row: i32) -> Result<(), String> {
|
||||
let mut index = None;
|
||||
for (i, r) in self.rows.iter().enumerate() {
|
||||
if r.r == row {
|
||||
index = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(i) = index {
|
||||
self.rows.remove(i);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_column_style(&mut self, column: i32) -> Result<(), String> {
|
||||
if !is_valid_column_number(column) {
|
||||
return Err(format!("Column number '{column}' is not valid."));
|
||||
}
|
||||
let cols = &mut self.cols;
|
||||
|
||||
let mut index = 0;
|
||||
let mut split = false;
|
||||
for c in cols.iter_mut() {
|
||||
let min = c.min;
|
||||
let max = c.max;
|
||||
if min <= column && column <= max {
|
||||
//
|
||||
split = true;
|
||||
break;
|
||||
}
|
||||
if column < min {
|
||||
// We passed, there is nothing to delete
|
||||
break;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
if split {
|
||||
let min = cols[index].min;
|
||||
let max = cols[index].max;
|
||||
let custom_width = cols[index].custom_width;
|
||||
let width = cols[index].width;
|
||||
let pre = Col {
|
||||
min,
|
||||
max: column - 1,
|
||||
width,
|
||||
custom_width,
|
||||
style: cols[index].style,
|
||||
};
|
||||
let col = Col {
|
||||
min: column,
|
||||
max: column,
|
||||
width,
|
||||
custom_width,
|
||||
style: None,
|
||||
};
|
||||
let post = Col {
|
||||
min: column + 1,
|
||||
max,
|
||||
width,
|
||||
custom_width,
|
||||
style: cols[index].style,
|
||||
};
|
||||
cols.remove(index);
|
||||
if column != max {
|
||||
cols.insert(index, post);
|
||||
}
|
||||
if custom_width {
|
||||
cols.insert(index, col);
|
||||
}
|
||||
if column != min {
|
||||
cols.insert(index, pre);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_cell_style(
|
||||
&mut self,
|
||||
row: i32,
|
||||
@@ -285,11 +363,12 @@ impl Worksheet {
|
||||
|
||||
/// Changes the width of a column.
|
||||
/// * If the column does not a have a width we simply add it
|
||||
/// * If it has, it might be part of a range and we ned to split the range.
|
||||
/// * If it has, it might be part of a range and we need to split the range.
|
||||
///
|
||||
/// Fails if column index is outside allowed range or width is negative.
|
||||
pub fn set_column_width(&mut self, column: i32, width: f64) -> Result<(), String> {
|
||||
self.set_column_width_and_style(column, width, None)
|
||||
let style = self.get_column_style(column)?;
|
||||
self.set_column_width_and_style(column, width, style)
|
||||
}
|
||||
|
||||
pub(crate) fn set_column_width_and_style(
|
||||
@@ -309,7 +388,7 @@ impl Worksheet {
|
||||
min: column,
|
||||
max: column,
|
||||
width: width / constants::COLUMN_WIDTH_FACTOR,
|
||||
custom_width: true,
|
||||
custom_width: width != constants::DEFAULT_COLUMN_WIDTH,
|
||||
style,
|
||||
};
|
||||
let mut index = 0;
|
||||
@@ -319,6 +398,7 @@ impl Worksheet {
|
||||
let max = c.max;
|
||||
if min <= column && column <= max {
|
||||
if min == column && max == column {
|
||||
c.style = style;
|
||||
c.width = width / constants::COLUMN_WIDTH_FACTOR;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -383,6 +463,23 @@ impl Worksheet {
|
||||
Ok(constants::DEFAULT_COLUMN_WIDTH)
|
||||
}
|
||||
|
||||
/// Returns the column style index if present
|
||||
pub fn get_column_style(&self, column: i32) -> Result<Option<i32>, String> {
|
||||
if !is_valid_column_number(column) {
|
||||
return Err(format!("Column number '{column}' is not valid."));
|
||||
}
|
||||
|
||||
let cols = &self.cols;
|
||||
for col in cols {
|
||||
let min = col.min;
|
||||
let max = col.max;
|
||||
if column >= min && column <= max {
|
||||
return Ok(col.style);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// Returns non empty cells in a column
|
||||
pub fn column_cell_references(&self, column: i32) -> Result<Vec<CellReferenceIndex>, String> {
|
||||
let mut column_cell_references: Vec<CellReferenceIndex> = Vec::new();
|
||||
|
||||
Reference in New Issue
Block a user