From 3219e01176ccb0a53beeddbe359f0dc143b23585 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Sun, 9 Feb 2025 20:10:40 -0500 Subject: [PATCH] wip: support column and row styling --- src/book/mod.rs | 153 ++++++++++++++------------------------ src/ui/mod.rs | 14 ++-- src/ui/render/viewport.rs | 11 +-- src/ui/test.rs | 52 +++++++++++++ 4 files changed, 117 insertions(+), 113 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index f93a37d..a83f658 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, Result}; use ironcalc::{ base::{ expressions::types::Area, - types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, + types::{SheetData, Style, Worksheet}, worksheet::WorksheetDimension, Model, UserModel, }, @@ -17,7 +17,12 @@ use crate::ui::Address; #[cfg(test)] mod test; -const COL_PIXELS: f64 = 5.0; +pub(crate) const COL_PIXELS: f64 = 5.0; +// NOTE(zaphar): This is stolen from ironcalc but ironcalc doesn't expose it +// publically. +pub(crate) const LAST_COLUMN: i32 = 16_384; +pub(crate) const LAST_ROW: i32 = 1_048_576; + #[derive(Debug, Clone)] pub struct AddressRange<'book> { @@ -281,82 +286,6 @@ impl Book { } } - fn get_column(&self, sheet: u32, col: usize) -> Result> { - Ok(self - .model - .get_model() - .workbook - .worksheet(sheet) - .map_err(|e| anyhow!("{}", e))? - .cols - .get(col)) - } - - fn get_row(&self, sheet: u32, col: usize) -> Result> { - Ok(self - .model - .get_model() - .workbook - .worksheet(sheet) - .map_err(|e| anyhow!("{}", e))? - .rows - .get(col)) - } - - pub fn get_column_style(&self, sheet: u32, col: usize) -> Result> { - // TODO(jwall): This is modeled a little weird. We should probably record - // the error *somewhere* but for the user there is nothing to be done except - // not use a style. - if let Some(col) = self.get_column(sheet, col)? { - if let Some(style_idx) = col.style.map(|idx| idx as usize) { - let styles = &self.model.get_model().workbook.styles; - if styles.cell_style_xfs.len() <= style_idx { - return Ok(Some(Style { - alignment: None, - num_fmt: styles.num_fmts[style_idx].format_code.clone(), - fill: styles.fills[style_idx].clone(), - font: styles.fonts[style_idx].clone(), - border: styles.borders[style_idx].clone(), - quote_prefix: false, - })); - } - } - } - return Ok(None); - } - - pub fn get_row_style(&self, sheet: u32, row: usize) -> Result> { - // TODO(jwall): This is modeled a little weird. We should probably record - // the error *somewhere* but for the user there is nothing to be done except - // not use a style. - if let Some(row) = self.get_row(sheet, row)? { - let style_idx = row.s as usize; - let styles = &self.model.get_model().workbook.styles; - if styles.cell_style_xfs.len() <= style_idx { - return Ok(Some(Style { - alignment: None, - num_fmt: styles.num_fmts[style_idx].format_code.clone(), - fill: styles.fills[style_idx].clone(), - font: styles.fonts[style_idx].clone(), - border: styles.borders[style_idx].clone(), - quote_prefix: false, - })); - } - } - return Ok(None); - } - - pub fn create_style(&mut self) -> Style { - Style { - alignment: None, - num_fmt: String::new(), - fill: Fill::default(), - font: Font::default(), - border: Border::default(), - quote_prefix: false, - } - } - pub fn set_cell_style(&mut self, style: &[(&str, &str)], area: &Area) -> Result<()> { for (path, val) in style { self.model @@ -366,28 +295,56 @@ impl Book { Ok(()) } - pub fn set_col_style(&mut self, style: &[(&str, &str)], sheet: u32, col: usize) -> Result<()> { - todo!() - //let idx = self.create_or_get_style_idx(style); - //let sheet = self.model.workbook.worksheet_mut(sheet) - // .map_err(|e| anyhow!("{}", e))?; - //let width = sheet.get_column_width(col as i32) - // .map_err(|e| anyhow!("{}", e))?; - //sheet.set_column_style(col as i32, idx) - // .map_err(|e| anyhow!("{}", e))?; - //sheet.set_column_width(col as i32, width) - // .map_err(|e| anyhow!("{}", e))?; - //Ok(()) + fn get_col_range(&self, sheet: u32, col_idx: usize) -> Area { + Area { + sheet, + row: 1, + column: col_idx as i32, + width: 1, + height: LAST_ROW, + } } - pub fn set_row_style(&mut self, style: &[(&str, &str)], sheet: u32, row: usize) -> Result<()> { - todo!() - //let idx = self.create_or_get_style_idx(style); - //self.model.workbook.worksheet_mut(sheet) - // .map_err(|e| anyhow!("{}", e))? - // .set_row_style(row as i32, idx) - // .map_err(|e| anyhow!("{}", e))?; - //Ok(()) + fn get_row_range(&self, sheet: u32, row_idx: usize) -> Area { + Area { + sheet, + row: row_idx as i32, + column: 1, + width: LAST_COLUMN, + height: 1, + } + } + + pub fn set_col_style( + &mut self, + style: &[(&str, &str)], + sheet: u32, + col_idx: usize, + ) -> Result<()> { + // TODO(jeremy): This is a little hacky and the underlying model + // supports a better mechanism but UserModel doesn't support it yet. + // https://github.com/ironcalc/IronCalc/issues/273 + // https://github.com/ironcalc/IronCalc/pull/276 is the coming fix. + // NOTE(jwall): Because of the number of cells required to modify + // this is crazy slow + let area = self.get_col_range(sheet, col_idx); + self.set_cell_style(style, &area)?; + Ok(()) + } + + pub fn set_row_style( + &mut self, + style: &[(&str, &str)], + sheet: u32, + row_idx: usize, + ) -> Result<()> { + // TODO(jeremy): This is a little hacky and the underlying model + // supports a better mechanism but UserModel doesn't support it yet. + // https://github.com/ironcalc/IronCalc/issues/273 + // https://github.com/ironcalc/IronCalc/pull/276 is the coming fix. + let area = self.get_row_range(sheet, row_idx); + self.set_cell_style(style, &area)?; + Ok(()) } /// Get a cells rendered content for display. diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 042c1a8..0f7f110 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,7 +1,7 @@ //! Ui rendering logic use std::{path::PathBuf, process::ExitCode}; -use crate::book::{AddressRange, Book}; +use crate::book::{self, AddressRange, Book}; use anyhow::{anyhow, Result}; use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers}; @@ -235,7 +235,7 @@ impl<'ws> Workspace<'ws> { /// Move a row down in the current sheet. pub fn move_down(&mut self) -> Result<()> { let mut loc = self.book.location.clone(); - if loc.row < render::viewport::LAST_ROW { + if loc.row < (book::LAST_ROW as usize) { loc.row += 1; self.book.move_to(&loc)?; } @@ -274,7 +274,7 @@ impl<'ws> Workspace<'ws> { /// Move a column to the left in the current sheet. pub fn move_right(&mut self) -> Result<()> { let mut loc = self.book.location.clone(); - if loc.col < render::viewport::LAST_COLUMN { + if loc.col < (book::LAST_COLUMN as usize) { loc.col += 1; self.book.move_to(&loc)?; } @@ -466,8 +466,8 @@ impl<'ws> Workspace<'ws> { Ok(None) } Ok(Some(Cmd::Quit)) => Ok(Some(ExitCode::SUCCESS)), - Ok(Some(Cmd::ColorRows(_count, color))) => { - let row_count = _count.unwrap_or(1); + Ok(Some(Cmd::ColorRows(count, color))) => { + let row_count = count.unwrap_or(1); let row = self.book.location.row; for r in row..(row + row_count) { self.book.set_row_style( @@ -478,8 +478,8 @@ impl<'ws> Workspace<'ws> { } Ok(None) } - Ok(Some(Cmd::ColorColumns(_count, color))) => { - let col_count = _count.unwrap_or(1); + Ok(Some(Cmd::ColorColumns(count, color))) => { + let col_count = count.unwrap_or(1); let col = self.book.location.col; for c in col..(col + col_count) { self.book.set_col_style( diff --git a/src/ui/render/viewport.rs b/src/ui/render/viewport.rs index ff1824f..e3bf60e 100644 --- a/src/ui/render/viewport.rs +++ b/src/ui/render/viewport.rs @@ -7,14 +7,9 @@ use ratatui::{ widgets::{Block, Cell, Row, StatefulWidget, Table, Widget}, }; +use crate::book; use super::{Address, Book, RangeSelection}; -// TODO(zaphar): Move this to the book module. -// NOTE(zaphar): This is stolen from ironcalc but ironcalc doesn't expose it -// publically. -pub(crate) const LAST_COLUMN: usize = 16_384; -pub(crate) const LAST_ROW: usize = 1_048_576; - /// A visible column to show in our Viewport. #[derive(Clone, Debug)] pub struct VisibleColumn { @@ -68,7 +63,7 @@ impl<'ws> Viewport<'ws> { let start_row = std::cmp::min(self.selected.row, state.prev_corner.row); let mut start = start_row; let mut end = start_row; - for row_idx in start_row..=LAST_ROW { + for row_idx in start_row..=(book::LAST_ROW as usize) { let updated_length = length + 1; if updated_length <= height { length = updated_length; @@ -95,7 +90,7 @@ impl<'ws> Viewport<'ws> { // We start out with a length of 5 already reserved let mut length = 5; let start_idx = std::cmp::min(self.selected.col, state.prev_corner.col); - for idx in start_idx..=LAST_COLUMN { + for idx in start_idx..=(book::LAST_COLUMN as usize) { let size = self.book.get_col_size(idx)? as u16; let updated_length = length + size; let col = VisibleColumn { idx, length: size }; diff --git a/src/ui/test.rs b/src/ui/test.rs index ffbeb02..356b695 100644 --- a/src/ui/test.rs +++ b/src/ui/test.rs @@ -2,6 +2,7 @@ use std::process::ExitCode; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crate::book; use crate::ui::cmd::parse_color; use crate::ui::{Address, Modality}; @@ -1221,6 +1222,57 @@ fn test_color_cells() { } } +#[test] +fn test_color_row() { + let mut ws = new_workspace(); + script() + .char(':') + .chars("color-rows red") + .enter() + .run(&mut ws) + .expect("Unable to run script"); + for ci in [1, book::LAST_COLUMN] { + let style = ws + .book + .get_cell_style(ws.book.current_sheet, &Address { row: 1, col: ci as usize }) + .expect("failed to get style"); + assert_eq!( + "#800000", + style + .fill + .bg_color + .expect(&format!("No background color set for {}:{}", 1, ci)) + .as_str() + ); + } +} + +#[test] +fn test_color_col() { + let mut ws = new_workspace(); + script() + .char(':') + .chars("color-columns red") + .enter() + .run(&mut ws) + .expect("Unable to run script"); + for ri in [1, book::LAST_ROW] { + let style = ws + .book + .get_cell_style(ws.book.current_sheet, &Address { row: ri as usize, col: 1 }) + .expect("failed to get style"); + assert_eq!( + "#800000", + style + .fill + .bg_color + .expect(&format!("No background color set for {}:{}", ri, 1)) + .as_str() + ); + } +} + + fn new_workspace<'a>() -> Workspace<'a> { Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook") }