Compare commits

..

1 Commits

Author SHA1 Message Date
a299d5e806 wip: Convert over to UserModel
Step 1: update ironcalc version and plug it in
2025-02-03 20:39:59 -05:00
4 changed files with 102 additions and 80 deletions

4
Cargo.lock generated
View File

@ -841,7 +841,7 @@ dependencies = [
[[package]] [[package]]
name = "ironcalc" name = "ironcalc"
version = "0.3.0" version = "0.3.0"
source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#99125f1fea1c8c72c61f8cba94d847ed3471a4af" source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#42d557d48567851102725bf3f7bef39a0baecb72"
dependencies = [ dependencies = [
"bitcode", "bitcode",
"chrono", "chrono",
@ -857,7 +857,7 @@ dependencies = [
[[package]] [[package]]
name = "ironcalc_base" name = "ironcalc_base"
version = "0.3.0" version = "0.3.0"
source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#99125f1fea1c8c72c61f8cba94d847ed3471a4af" source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#42d557d48567851102725bf3f7bef39a0baecb72"
dependencies = [ dependencies = [
"bitcode", "bitcode",
"chrono", "chrono",

View File

@ -3,7 +3,10 @@ use std::cmp::max;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use ironcalc::{ use ironcalc::{
base::{ base::{
expressions::types::Area, types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, worksheet::WorksheetDimension, Model, UserModel expressions::types::Area,
types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet},
worksheet::WorksheetDimension,
Model, UserModel,
}, },
export::save_xlsx_to_writer, export::save_xlsx_to_writer,
import::load_from_xlsx, import::load_from_xlsx,
@ -110,6 +113,7 @@ impl Book {
self.model.evaluate(); self.model.evaluate();
} }
// TODO(zaphar): Should I support ICalc?
/// Construct a new book from a path. /// Construct a new book from a path.
pub fn new_from_xlsx_with_locale(path: &str, locale: &str, tz: &str) -> Result<Self> { pub fn new_from_xlsx_with_locale(path: &str, locale: &str, tz: &str) -> Result<Self> {
Ok(Self::from_model(load_from_xlsx(path, locale, tz)?)) Ok(Self::from_model(load_from_xlsx(path, locale, tz)?))
@ -140,19 +144,18 @@ impl Book {
Ok(&self.get_sheet()?.name) Ok(&self.get_sheet()?.name)
} }
pub fn set_sheet_name(&mut self, idx: u32, sheet_name: &str) -> Result<()> { pub fn set_sheet_name(&mut self, idx: usize, sheet_name: &str) -> Result<()> {
self.model.rename_sheet(idx, sheet_name).map_err(|e| anyhow!(e))?; self.get_sheet_by_idx_mut(idx)?.set_name(sheet_name);
Ok(()) Ok(())
} }
pub fn new_sheet(&mut self, sheet_name: Option<&str>) -> Result<()> { pub fn new_sheet(&mut self, sheet_name: Option<&str>) -> Result<()> {
self.model.new_sheet().map_err(|e| anyhow!(e))?; todo!("We need to figure out how to find the new sheet index so we can rename it");
let idx = self.model.get_selected_sheet(); //let (_, idx) = self.model.new_sheet();
if let Some(name) = sheet_name { //if let Some(name) = sheet_name {
self.set_sheet_name(idx, name)?; // self.set_sheet_name(idx as usize, name)?;
} //}
self.model.set_selected_sheet(self.current_sheet).map_err(|e| anyhow!(e))?; //Ok(())
Ok(())
} }
/// Get the sheet data for the current worksheet. /// Get the sheet data for the current worksheet.
@ -179,8 +182,6 @@ impl Book {
.skip(1) .skip(1)
{ {
// TODO(jeremy): Is there a better way to do this using UserModel? // TODO(jeremy): Is there a better way to do this using UserModel?
// Looks like this is the recommended way:
// https://docs.rs/ironcalc_base/latest/ironcalc_base/struct.UserModel.html#method.auto_fill_columns
let contents = self let contents = self
.model .model
.get_model() .get_model()
@ -227,7 +228,13 @@ impl Book {
} }
pub fn clear_cell_range(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> { pub fn clear_cell_range(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> {
let area = calculate_area(sheet, start, end); let area = Area {
sheet,
row: start.row as i32,
column: start.col as i32,
width: (end.row - start.row) as i32,
height: (end.col - end.row) as i32,
};
self.model self.model
.range_clear_contents(&area) .range_clear_contents(&area)
.map_err(|s| anyhow!("Unable to clear cell contents {}", s))?; .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?;
@ -248,7 +255,13 @@ impl Book {
} }
pub fn clear_cell_range_all(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> { pub fn clear_cell_range_all(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> {
let area = calculate_area(sheet, start, end); let area = Area {
sheet,
row: start.row as i32,
column: start.col as i32,
width: (end.row - start.row) as i32,
height: (end.col - end.row) as i32,
};
self.model.range_clear_all(&area) self.model.range_clear_all(&area)
.map_err(|s| anyhow!("Unable to clear cell contents {}", s))?; .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?;
Ok(()) Ok(())
@ -334,15 +347,14 @@ impl Book {
} }
} }
pub fn set_cell_style(&mut self, style: &[(&str, &str)], area: &Area) -> Result<()> { pub fn set_cell_style(&mut self, style: &Style, sheet: u32, cell: &Address) -> Result<()> {
for (path, val) in style { todo!()
self.model.update_range_style(area, path, val) //self.model.set_cell_style(sheet, cell.row as i32, cell.col as i32, style)
.map_err(|s| anyhow!("Unable to format cell {}", s))?; // .map_err(|s| anyhow!("Unable to format cell {}", s))?;
} //Ok(())
Ok(())
} }
pub fn set_col_style(&mut self, style: &[(&str, &str)], sheet: u32, col: usize) -> Result<()> { pub fn set_col_style(&mut self, style: &Style, sheet: u32, col: usize) -> Result<()> {
todo!() todo!()
//let idx = self.create_or_get_style_idx(style); //let idx = self.create_or_get_style_idx(style);
//let sheet = self.model.workbook.worksheet_mut(sheet) //let sheet = self.model.workbook.worksheet_mut(sheet)
@ -356,7 +368,7 @@ impl Book {
//Ok(()) //Ok(())
} }
pub fn set_row_style(&mut self, style: &[(&str, &str)], sheet: u32, row: usize) -> Result<()> { pub fn set_row_style(&mut self, style: &Style, sheet: u32, row: usize) -> Result<()> {
todo!() todo!()
//let idx = self.create_or_get_style_idx(style); //let idx = self.create_or_get_style_idx(style);
//self.model.workbook.worksheet_mut(sheet) //self.model.workbook.worksheet_mut(sheet)
@ -396,21 +408,21 @@ impl Book {
/// Update the current cell in a book. /// Update the current cell in a book.
/// This update won't be reflected until you call `Book::evaluate`. /// This update won't be reflected until you call `Book::evaluate`.
pub fn edit_current_cell<S: AsRef<str>>(&mut self, value: S) -> Result<()> { pub fn edit_current_cell<S: Into<String>>(&mut self, value: S) -> Result<()> {
self.update_cell(&self.location.clone(), value)?; self.update_cell(&self.location.clone(), value)?;
Ok(()) Ok(())
} }
/// Update an entry in the current sheet for a book. /// Update an entry in the current sheet for a book.
/// This update won't be reflected until you call `Book::evaluate`. /// This update won't be reflected until you call `Book::evaluate`.
pub fn update_cell<S: AsRef<str>>(&mut self, location: &Address, value: S) -> Result<()> { pub fn update_cell<S: Into<String>>(&mut self, location: &Address, value: S) -> Result<()> {
self.model self.model
.set_user_input( .set_user_input(
self.current_sheet, self.current_sheet,
location.row as i32, location.row as i32,
location.col as i32, location.col as i32,
// TODO(jwall): This could probably be made more efficient // TODO(jwall): This could probably be made more efficient
value.as_ref(), &value.into(),
) )
.map_err(|e| anyhow!("Invalid cell contents: {}", e))?; .map_err(|e| anyhow!("Invalid cell contents: {}", e))?;
Ok(()) Ok(())
@ -455,21 +467,16 @@ impl Book {
/// Get column size /// Get column size
pub fn get_col_size(&self, idx: usize) -> Result<usize> { pub fn get_col_size(&self, idx: usize) -> Result<usize> {
self.get_column_size_for_sheet(self.current_sheet, idx) Ok((self
} .get_sheet()?
.get_column_width(idx as i32)
pub fn get_column_size_for_sheet(&self, sheet: u32, idx: usize) -> std::result::Result<usize, anyhow::Error> {
Ok((self.model.get_column_width(sheet, idx as i32)
.map_err(|e| anyhow!("Error getting column width: {:?}", e))? .map_err(|e| anyhow!("Error getting column width: {:?}", e))?
/ COL_PIXELS) as usize) / COL_PIXELS) as usize)
} }
pub fn set_col_size(&mut self, col: usize, width: usize) -> Result<()> { pub fn set_col_size(&mut self, idx: usize, cols: usize) -> Result<()> {
self.set_column_size_for_sheet(self.current_sheet, col, width) self.get_sheet_mut()?
} .set_column_width(idx as i32, cols as f64 * COL_PIXELS)
pub fn set_column_size_for_sheet(&mut self, sheet: u32, col: usize, width: usize) -> std::result::Result<(), anyhow::Error> {
self.model.set_column_width(sheet, col as i32, width as f64 * COL_PIXELS)
.map_err(|e| anyhow!("Error setting column width: {:?}", e))?; .map_err(|e| anyhow!("Error setting column width: {:?}", e))?;
Ok(()) Ok(())
} }
@ -510,28 +517,29 @@ impl Book {
} }
pub fn select_next_sheet(&mut self) { pub fn select_next_sheet(&mut self) {
// TODO(jwall): Is there a cleaner way to do this with UserModel?
let len = self.model.get_model().workbook.worksheets.len() as u32; let len = self.model.get_model().workbook.worksheets.len() as u32;
let mut next = self.current_sheet + 1; let mut next = self.current_sheet + 1;
if next == len { if next == len {
next = 0; next = 0;
} }
self.model.set_selected_sheet(next).expect("Unexpected error selecting sheet");
self.current_sheet = next; self.current_sheet = next;
} }
pub fn select_prev_sheet(&mut self) { pub fn select_prev_sheet(&mut self) {
// TODO(jwall): Is there a cleaner way to do this with UserModel?
let len = self.model.get_model().workbook.worksheets.len() as u32; let len = self.model.get_model().workbook.worksheets.len() as u32;
let next = if self.current_sheet == 0 { let next = if self.current_sheet == 0 {
len - 1 len - 1
} else { } else {
self.current_sheet - 1 self.current_sheet - 1
}; };
self.model.set_selected_sheet(next).expect("Unexpected error selecting sheet");
self.current_sheet = next; self.current_sheet = next;
} }
/// Select a sheet by id. /// Select a sheet by id.
pub fn select_sheet_by_id(&mut self, id: u32) -> bool { pub fn select_sheet_by_id(&mut self, id: u32) -> bool {
// TODO(jwall): Is there a cleaner way to do this with UserModel?
if let Some((idx, _sheet)) = self if let Some((idx, _sheet)) = self
.model.get_model() .model.get_model()
.workbook .workbook
@ -540,7 +548,6 @@ impl Book {
.enumerate() .enumerate()
.find(|(_idx, sheet)| sheet.sheet_id == id) .find(|(_idx, sheet)| sheet.sheet_id == id)
{ {
self.model.set_selected_sheet(idx as u32).expect("Unexpected error selecting sheet");
self.current_sheet = idx as u32; self.current_sheet = idx as u32;
return true; return true;
} }
@ -550,8 +557,6 @@ impl Book {
/// Get the current `Worksheet`. /// Get the current `Worksheet`.
pub(crate) fn get_sheet(&self) -> Result<&Worksheet> { pub(crate) fn get_sheet(&self) -> Result<&Worksheet> {
// TODO(jwall): Is there a cleaner way to do this with UserModel? // TODO(jwall): Is there a cleaner way to do this with UserModel?
// Looks like it should be done with:
// https://docs.rs/ironcalc_base/latest/ironcalc_base/struct.UserModel.html#method.get_worksheets_properties
Ok(self Ok(self
.model.get_model() .model.get_model()
.workbook .workbook
@ -559,10 +564,17 @@ impl Book {
.map_err(|s| anyhow!("Invalid Worksheet id: {}: error: {}", self.current_sheet, s))?) .map_err(|s| anyhow!("Invalid Worksheet id: {}: error: {}", self.current_sheet, s))?)
} }
pub(crate) fn get_sheet_mut(&mut self) -> Result<&mut Worksheet> {
todo!("Is there a clean way to do this with UserModel?")
//Ok(self
// .model.get_model()
// .workbook
// .worksheet_mut(self.current_sheet)
// .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?)
}
pub(crate) fn get_sheet_name_by_idx(&self, idx: usize) -> Result<&str> { pub(crate) fn get_sheet_name_by_idx(&self, idx: usize) -> Result<&str> {
// TODO(jwall): Is there a cleaner way to do this with UserModel? // TODO(jwall): Is there a cleaner way to do this with UserModel?
// Looks like it should be done with:
// https://docs.rs/ironcalc_base/latest/ironcalc_base/struct.UserModel.html#method.get_worksheets_properties
Ok(&self Ok(&self
.model.get_model() .model.get_model()
.workbook .workbook
@ -570,17 +582,14 @@ impl Book {
.map_err(|s| anyhow!("Invalid Worksheet: {}", s))? .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?
.name) .name)
} }
pub(crate) fn get_sheet_by_idx_mut(&mut self, idx: usize) -> Result<&mut Worksheet> {
todo!("Is there a clean way to do this with UserModel?")
//Ok(self
// .model
// .workbook
// .worksheet_mut(idx as u32)
// .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?)
} }
fn calculate_area(sheet: u32, start: Address, end: Address) -> Area {
let area = Area {
sheet,
row: start.row as i32,
column: start.col as i32,
height: (end.row - start.row + 1) as i32,
width: (end.col - start.col + 1) as i32,
};
area
} }
impl Default for Book { impl Default for Book {

View File

@ -5,7 +5,7 @@ use crate::book::{AddressRange, Book};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers}; use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
use ironcalc::base::{expressions::types::Area, Model}; use ironcalc::base::Model;
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::{Constraint, Flex, Layout}, layout::{Constraint, Flex, Layout},
@ -131,7 +131,7 @@ impl<'ws> AppState<'ws> {
} }
} }
// TODO(jwall): Should we just be using `Area` for this?. // TODO(jwall): This should probably move to a different module.
/// The Address in a Table. /// The Address in a Table.
#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)] #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)]
pub struct Address { pub struct Address {
@ -445,11 +445,11 @@ impl<'ws> Workspace<'ws> {
Ok(Some(Cmd::RenameSheet(idx, name))) => { Ok(Some(Cmd::RenameSheet(idx, name))) => {
match idx { match idx {
Some(idx) => { Some(idx) => {
self.book.set_sheet_name(idx as u32, name)?; self.book.set_sheet_name(idx, name)?;
} }
_ => { _ => {
self.book self.book
.set_sheet_name(self.book.current_sheet, name)?; .set_sheet_name(self.book.current_sheet as usize, name)?;
} }
} }
Ok(None) Ok(None)
@ -469,7 +469,13 @@ impl<'ws> Workspace<'ws> {
let row_count = _count.unwrap_or(1); let row_count = _count.unwrap_or(1);
let row = self.book.location.row; let row = self.book.location.row;
for r in row..(row+row_count) { for r in row..(row+row_count) {
self.book.set_row_style(&[("fill.bg_color", color)], self.book.current_sheet, r)?; let mut style = if let Some(style) = self.book.get_row_style(self.book.current_sheet, r)? {
style
} else {
self.book.create_style()
};
style.fill.bg_color = Some(color.to_string());
self.book.set_row_style(&style, self.book.current_sheet, r)?;
} }
Ok(None) Ok(None)
} }
@ -477,31 +483,36 @@ impl<'ws> Workspace<'ws> {
let col_count = _count.unwrap_or(1); let col_count = _count.unwrap_or(1);
let col = self.book.location.col; let col = self.book.location.col;
for c in col..(col+col_count) { for c in col..(col+col_count) {
self.book.set_col_style(&[("fill.bg_color", color)], self.book.current_sheet, c)?; let mut style = if let Some(style) = self.book.get_column_style(self.book.current_sheet, c)? {
style
} else {
self.book.create_style()
};
style.fill.bg_color = Some(color.to_string());
self.book.set_col_style(&style, self.book.current_sheet, c)?;
} }
Ok(None) Ok(None)
} }
Ok(Some(Cmd::ColorCell(color))) => { Ok(Some(Cmd::ColorCell(color))) => {
if let Some((start, end)) = self.state.range_select.get_range() {
for ri in start.row..=end.row {
for ci in start.col..=end.col {
let address = Address { row: ri, col: ci };
let sheet = self.book.current_sheet; let sheet = self.book.current_sheet;
let area = if let Some((start, end)) = self.state.range_select.get_range() { let mut style = self.book.get_cell_style(sheet, &address)
Area { .expect("I think this should be impossible.").clone();
sheet, style.fill.bg_color = Some(color.to_string());
row: start.row as i32, self.book.set_cell_style(&style, sheet, &address)?;
column: start.col as i32, }
width: (end.col - start.col) as i32,
height: (end.row - start.row) as i32
} }
} else { } else {
let address = self.book.location.clone(); let address = self.book.location.clone();
Area { let sheet = self.book.current_sheet;
sheet, let mut style = self.book.get_cell_style(sheet, &address)
row: address.row as i32, .expect("I think this should be impossible.").clone();
column: address.col as i32, style.fill.bg_color = Some(color.to_string());
width: 1, self.book.set_cell_style(&style, sheet, &address)?;
height: 1
} }
};
self.book.set_cell_style(&[("fill.bg_color", color)], &area)?;
Ok(None) Ok(None)
} }
Ok(None) => { Ok(None) => {
@ -539,7 +550,7 @@ impl<'ws> Workspace<'ws> {
self.handle_numeric_prefix(d); self.handle_numeric_prefix(d);
} }
KeyCode::Char('D') => { KeyCode::Char('D') => {
if let Some((start, end)) = dbg!(self.state.range_select.get_range()) { if let Some((start, end)) = self.state.range_select.get_range() {
self.book.clear_cell_range_all( self.book.clear_cell_range_all(
self.state self.state
.range_select .range_select
@ -616,6 +627,7 @@ impl<'ws> Workspace<'ws> {
.modifiers .modifiers
.contains(KeyModifiers::CONTROL) => .contains(KeyModifiers::CONTROL) =>
{ {
// TODO(zaphar): Share the algorithm below between both copies
self.copy_range(true)?; self.copy_range(true)?;
self.exit_range_select_mode()?; self.exit_range_select_mode()?;
} }

View File

@ -248,6 +248,7 @@ pub(crate) fn map_color(color: Option<&String>, otherwise: Color) -> Color {
candidate => { candidate => {
// TODO(jeremy): Should we support more syntaxes than hex string? // TODO(jeremy): Should we support more syntaxes than hex string?
// hsl(...) ?? // hsl(...) ??
// rgb(...) ??
if candidate.starts_with("#") { if candidate.starts_with("#") {
if let Ok(rgb) = colorsys::Rgb::from_hex_str(candidate) { if let Ok(rgb) = colorsys::Rgb::from_hex_str(candidate) {
// Note that the colorsys rgb model clamps the f64 values to no more // Note that the colorsys rgb model clamps the f64 values to no more