//! Sheet features beyond cells: tables, conditional formatting, data validation, comments, //! hyperlinks, charts, images, autofilter, print settings. use gridcraft_core::{CellRef, RangeRef}; use serde::{Deserialize, Serialize}; use crate::style::{Color, Style}; // ---------------------------------------------------------------- tables #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum TotalsFn { #[default] None, Average, Count, CountNums, Max, Min, Sum, StdDev, Var, Custom, } impl TotalsFn { /// SUBTOTAL function number. pub fn subtotal_code(&self) -> Option { Some(match self { TotalsFn::Average => 101, TotalsFn::Count => 103, TotalsFn::CountNums => 102, TotalsFn::Max => 104, TotalsFn::Min => 105, TotalsFn::Sum => 109, TotalsFn::StdDev => 107, TotalsFn::Var => 110, _ => return None, }) } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TableColumn { pub name: String, pub totals: TotalsFn, /// Totals row label (first column) or custom formula. pub totals_label: Option, /// Calculated column formula. pub formula: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Table { pub id: u32, pub name: String, /// Whole table including header and totals rows. pub range: RangeRef, pub header_row: bool, pub totals_row: bool, pub columns: Vec, pub style: String, pub banded_rows: bool, pub banded_cols: bool, pub first_col: bool, pub last_col: bool, pub filter_button: bool, } impl Table { pub fn data_range(&self) -> Option { let r0 = self.range.start.row + self.header_row as u32; let r1 = self.range.end.row.checked_sub(self.totals_row as u32)?; if r1 < r0 { return None; } Some(RangeRef::new(CellRef::new(r0, self.range.start.col), CellRef::new(r1, self.range.end.col))) } pub fn header_range(&self) -> Option { self.header_row.then(|| RangeRef::new(self.range.start, CellRef::new(self.range.start.row, self.range.end.col))) } pub fn totals_range(&self) -> Option { self.totals_row.then(|| RangeRef::new(CellRef::new(self.range.end.row, self.range.start.col), self.range.end)) } pub fn column_index(&self, name: &str) -> Option { self.columns.iter().position(|c| c.name.eq_ignore_ascii_case(name)) } } // ---------------------------------------------------------------- conditional formatting #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum CfOperator { Between, NotBetween, Equal, NotEqual, Greater, Less, GreaterOrEqual, LessOrEqual, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum CfValueKind { Min, Max, Number(f64), Percent(f64), Percentile(f64), Formula(String), } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum CfRule { /// Cell value compared with one or two formulas. CellIs { op: CfOperator, a: String, b: Option, style: Box