UPDATE: Adds web app

This commit is contained in:
Nicolás Hatcher
2024-04-07 14:19:06 +02:00
parent 0ba80035d2
commit 9d83cc87c9
85 changed files with 25210 additions and 346 deletions

View File

@@ -58,3 +58,4 @@ pub mod mock_time;
pub use model::get_milliseconds_since_epoch;
pub use model::Model;
pub use user_model::UserModel;
pub use user_model::BorderArea;

View File

@@ -353,7 +353,14 @@ impl Model {
let now = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
let mut views = HashMap::new();
views.insert(0, WorkbookView { sheet: 0 });
views.insert(
0,
WorkbookView {
sheet: 0,
window_width: 800,
window_height: 600,
},
);
// String versions of the locale are added here to simplify the serialize/deserialize logic
let workbook = Workbook {

View File

@@ -33,6 +33,10 @@ pub struct WorkbookSettings {
pub struct WorkbookView {
/// The index of the currently selected sheet.
pub sheet: u32,
/// The current width of the window
pub window_width: i64,
/// The current heigh of the window
pub window_height: i64,
}
/// An internal representation of an IronCalc Workbook

View File

@@ -2,7 +2,6 @@
use std::{collections::HashMap, fmt::Debug};
use bitcode::{Decode, Encode};
use serde::{Deserialize, Serialize};
use crate::{
@@ -13,180 +12,35 @@ use crate::{
},
model::Model,
types::{
Alignment, BorderItem, BorderStyle, Cell, CellType, Col, HorizontalAlignment, Row,
SheetProperties, Style, VerticalAlignment,
Alignment, BorderItem, BorderStyle, CellType, Col, HorizontalAlignment, SheetProperties,
Style, VerticalAlignment,
},
utils::is_valid_hex_color,
};
use crate::user_model::history::{
ColumnData, Diff, DiffList, DiffType, History, QueueDiffs, RowData,
};
#[derive(Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq, Debug))]
pub struct SelectedView {
pub sheet: u32,
pub row: i32,
pub column: i32,
pub range: [i32; 4],
pub top_row: i32,
pub left_column: i32,
pub enum BorderType {
All,
Inner,
Outer,
Top,
Right,
Bottom,
Left,
CenterH,
CenterV,
None,
}
#[derive(Clone, Encode, Decode)]
struct RowData {
row: Option<Row>,
data: HashMap<i32, Cell>,
}
#[derive(Clone, Encode, Decode)]
struct ColumnData {
column: Option<Col>,
data: HashMap<i32, Cell>,
}
#[derive(Clone, Encode, Decode)]
enum Diff {
// Cell diffs
SetCellValue {
sheet: u32,
row: i32,
column: i32,
new_value: String,
old_value: Box<Option<Cell>>,
},
CellClearContents {
sheet: u32,
row: i32,
column: i32,
old_value: Box<Option<Cell>>,
},
CellClearAll {
sheet: u32,
row: i32,
column: i32,
old_value: Box<Option<Cell>>,
old_style: Box<Style>,
},
SetCellStyle {
sheet: u32,
row: i32,
column: i32,
old_value: Box<Style>,
new_value: Box<Style>,
},
// Column and Row diffs
SetColumnWidth {
sheet: u32,
column: i32,
new_value: f64,
old_value: f64,
},
SetRowHeight {
sheet: u32,
row: i32,
new_value: f64,
old_value: f64,
},
InsertRow {
sheet: u32,
row: i32,
},
DeleteRow {
sheet: u32,
row: i32,
old_data: Box<RowData>,
},
InsertColumn {
sheet: u32,
column: i32,
},
DeleteColumn {
sheet: u32,
column: i32,
old_data: Box<ColumnData>,
},
SetFrozenRowsCount {
sheet: u32,
new_value: i32,
old_value: i32,
},
SetFrozenColumnsCount {
sheet: u32,
new_value: i32,
old_value: i32,
},
DeleteSheet {
sheet: u32,
},
NewSheet {
index: u32,
name: String,
},
RenameSheet {
index: u32,
old_value: String,
new_value: String,
},
SetSheetColor {
index: u32,
old_value: String,
new_value: String,
},
SetShowGridLines {
sheet: u32,
old_value: bool,
new_value: bool,
}, // FIXME: we are missing SetViewDiffs
}
type DiffList = Vec<Diff>;
#[derive(Default)]
struct History {
undo_stack: Vec<DiffList>,
redo_stack: Vec<DiffList>,
}
impl History {
fn push(&mut self, diff_list: DiffList) {
self.undo_stack.push(diff_list);
self.redo_stack = vec![];
}
fn undo(&mut self) -> Option<Vec<Diff>> {
match self.undo_stack.pop() {
Some(diff_list) => {
self.redo_stack.push(diff_list.clone());
Some(diff_list)
}
None => None,
}
}
fn redo(&mut self) -> Option<Vec<Diff>> {
match self.redo_stack.pop() {
Some(diff_list) => {
self.undo_stack.push(diff_list.clone());
Some(diff_list)
}
None => None,
}
}
fn clear(&mut self) {
self.redo_stack = vec![];
self.undo_stack = vec![];
}
}
#[derive(Clone, Encode, Decode)]
enum DiffType {
Undo,
Redo,
}
#[derive(Clone, Encode, Decode)]
struct QueueDiffs {
r#type: DiffType,
list: DiffList,
/// This is the struct for a border area
#[derive(Serialize, Deserialize)]
pub struct BorderArea {
item: BorderItem,
r#type: BorderType,
}
fn boolean(value: &str) -> Result<bool, String> {
@@ -292,7 +146,7 @@ fn vertical(value: &str) -> Result<VerticalAlignment, String> {
/// # }
/// ```
pub struct UserModel {
model: Model,
pub(crate) model: Model,
history: History,
send_queue: Vec<QueueDiffs>,
pause_evaluation: bool,
@@ -828,6 +682,154 @@ impl UserModel {
self.model.set_frozen_columns(sheet, frozen_columns)
}
/// Paste `styles` in the selected area
pub fn on_paste_styles(&mut self, styles: &[Vec<Style>]) -> Result<(), String> {
let styles_heigh = styles.len() as i32;
let styles_width = styles[0].len() as i32;
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
return Ok(());
};
let range = if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
if let Some(view) = worksheet.views.get(&self.model.view_id) {
view.range
} else {
return Ok(());
}
} else {
return Ok(());
};
// If the pasted area is smaller than the selected area we increase it
let [row_start, column_start, row_end, column_end] = range;
let last_row = row_end.max(row_start + styles_heigh - 1);
let last_column = column_end.max(column_start + styles_width - 1);
let mut diff_list = Vec::new();
for row in row_start..=last_row {
for column in column_start..=last_column {
let row_index = ((row - row_start) % styles_heigh) 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);
self.model.set_cell_style(sheet, row, column, style)?;
diff_list.push(Diff::SetCellStyle {
sheet,
row,
column,
old_value: Box::new(old_value),
new_value: Box::new(style.clone()),
});
}
}
self.push_diff_list(diff_list);
// select the pasted range
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
view.range = [row_start, column_start, last_row, last_column];
}
}
Ok(())
}
/// Sets the border
pub fn set_area_with_border(
&mut self,
range: &Area,
border_area: &BorderArea,
) -> Result<(), String> {
let sheet = range.sheet;
let mut diff_list = Vec::new();
let last_row = range.row + range.height - 1;
let last_column = range.column + range.width - 1;
for row in range.row..=last_row {
for column in range.column..=last_column {
let old_value = self.model.get_style_for_cell(sheet, row, column);
let mut style = old_value.clone();
// First remove all existing borders
style.border.top = None;
style.border.right = None;
style.border.bottom = None;
style.border.left = None;
match border_area.r#type {
BorderType::All => {
style.border.top = Some(border_area.item.clone());
style.border.right = Some(border_area.item.clone());
style.border.bottom = Some(border_area.item.clone());
style.border.left = Some(border_area.item.clone());
}
BorderType::Inner => {
if row != range.row {
style.border.top = Some(border_area.item.clone());
}
if row != last_row {
style.border.bottom = Some(border_area.item.clone());
}
if column != range.column {
style.border.left = Some(border_area.item.clone());
}
if column != last_column {
style.border.right = Some(border_area.item.clone());
}
}
BorderType::Outer => {
if row == range.row {
style.border.top = Some(border_area.item.clone());
}
if row == last_row {
style.border.bottom = Some(border_area.item.clone());
}
if column == range.column {
style.border.left = Some(border_area.item.clone());
}
if column == last_column {
style.border.right = Some(border_area.item.clone());
}
}
BorderType::Top => style.border.top = Some(border_area.item.clone()),
BorderType::Right => style.border.right = Some(border_area.item.clone()),
BorderType::Bottom => style.border.bottom = Some(border_area.item.clone()),
BorderType::Left => style.border.left = Some(border_area.item.clone()),
BorderType::CenterH => {
if row != range.row {
style.border.top = Some(border_area.item.clone());
}
if row != last_row {
style.border.bottom = Some(border_area.item.clone());
}
}
BorderType::CenterV => {
if column != range.column {
style.border.left = Some(border_area.item.clone());
}
if column != last_column {
style.border.right = Some(border_area.item.clone());
}
}
BorderType::None => {
// noop, we already removed all the borders
}
}
self.model.set_cell_style(sheet, row, column, &style)?;
diff_list.push(Diff::SetCellStyle {
sheet,
row,
column,
old_value: Box::new(old_value),
new_value: Box::new(style),
});
}
}
self.push_diff_list(diff_list);
Ok(())
}
/// Updates the range with a cell style.
/// See also:
/// * [Model::set_cell_style]
@@ -1154,166 +1156,6 @@ impl UserModel {
self.model.get_worksheets_properties()
}
/// Returns the selected sheet index
pub fn get_selected_sheet(&self) -> u32 {
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
}
}
/// Returns the selected cell
pub fn get_selected_cell(&self) -> (u32, i32, i32) {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
};
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
if let Some(view) = worksheet.views.get(&self.model.view_id) {
return (sheet, view.row, view.column);
}
}
// return a safe default
(0, 1, 1)
}
/// Returns selected view
pub fn get_selected_view(&self) -> SelectedView {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
};
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
if let Some(view) = worksheet.views.get(&self.model.view_id) {
return SelectedView {
sheet,
row: view.row,
column: view.column,
range: view.range,
top_row: view.top_row,
left_column: view.left_column,
};
}
}
// return a safe default
SelectedView {
sheet: 0,
row: 1,
column: 1,
range: [1, 1, 1, 1],
top_row: 1,
left_column: 1,
}
}
/// Sets the the selected sheet
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<(), String> {
if self.model.workbook.worksheet(sheet).is_err() {
return Err(format!("Invalid worksheet index {}", sheet));
}
if let Some(view) = self.model.workbook.views.get_mut(&0) {
view.sheet = sheet;
}
Ok(())
}
/// Sets the selected cell
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<(), String> {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
};
if !is_valid_column_number(column) {
return Err(format!("Invalid column: '{column}'"));
}
if !is_valid_row(row) {
return Err(format!("Invalid row: '{row}'"));
}
if self.model.workbook.worksheet(sheet).is_err() {
return Err(format!("Invalid worksheet index {}", sheet));
}
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&0) {
view.row = row;
view.column = column;
view.range = [row, column, row, column];
}
}
Ok(())
}
/// Sets the selected range
pub fn set_selected_range(
&mut self,
start_row: i32,
start_column: i32,
end_row: i32,
end_column: i32,
) -> Result<(), String> {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
};
if !is_valid_column_number(start_column) {
return Err(format!("Invalid column: '{start_column}'"));
}
if !is_valid_column_number(start_row) {
return Err(format!("Invalid row: '{start_row}'"));
}
if !is_valid_column_number(end_column) {
return Err(format!("Invalid column: '{end_column}'"));
}
if !is_valid_column_number(end_row) {
return Err(format!("Invalid row: '{end_row}'"));
}
if self.model.workbook.worksheet(sheet).is_err() {
return Err(format!("Invalid worksheet index {}", sheet));
}
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&0) {
view.range = [start_row, start_column, end_row, end_column];
}
}
Ok(())
}
/// Sets the value of the first visible cell
pub fn set_top_left_visible_cell(
&mut self,
top_row: i32,
left_column: i32,
) -> Result<(), String> {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
};
if !is_valid_column_number(left_column) {
return Err(format!("Invalid column: '{left_column}'"));
}
if !is_valid_column_number(top_row) {
return Err(format!("Invalid row: '{top_row}'"));
}
if self.model.workbook.worksheet(sheet).is_err() {
return Err(format!("Invalid worksheet index {}", sheet));
}
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&0) {
view.top_row = top_row;
view.left_column = left_column;
}
}
Ok(())
}
/// Set the gid lines in the worksheet to visible (`true`) or hidden (`false`)
pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<(), String> {
let old_value = self.model.workbook.worksheet(sheet)?.show_grid_lines;
@@ -1643,7 +1485,7 @@ impl UserModel {
mod tests {
use crate::{
types::{HorizontalAlignment, VerticalAlignment},
user_model::{horizontal, vertical},
user_model::common::{horizontal, vertical},
};
#[test]

View File

@@ -0,0 +1,164 @@
use std::collections::HashMap;
use bitcode::{Decode, Encode};
use crate::types::{Cell, Col, Row, Style};
#[derive(Clone, Encode, Decode)]
pub(crate) struct RowData {
pub(crate) row: Option<Row>,
pub(crate) data: HashMap<i32, Cell>,
}
#[derive(Clone, Encode, Decode)]
pub(crate) struct ColumnData {
pub(crate) column: Option<Col>,
pub(crate) data: HashMap<i32, Cell>,
}
#[derive(Clone, Encode, Decode)]
pub(crate) enum Diff {
// Cell diffs
SetCellValue {
sheet: u32,
row: i32,
column: i32,
new_value: String,
old_value: Box<Option<Cell>>,
},
CellClearContents {
sheet: u32,
row: i32,
column: i32,
old_value: Box<Option<Cell>>,
},
CellClearAll {
sheet: u32,
row: i32,
column: i32,
old_value: Box<Option<Cell>>,
old_style: Box<Style>,
},
SetCellStyle {
sheet: u32,
row: i32,
column: i32,
old_value: Box<Style>,
new_value: Box<Style>,
},
// Column and Row diffs
SetColumnWidth {
sheet: u32,
column: i32,
new_value: f64,
old_value: f64,
},
SetRowHeight {
sheet: u32,
row: i32,
new_value: f64,
old_value: f64,
},
InsertRow {
sheet: u32,
row: i32,
},
DeleteRow {
sheet: u32,
row: i32,
old_data: Box<RowData>,
},
InsertColumn {
sheet: u32,
column: i32,
},
DeleteColumn {
sheet: u32,
column: i32,
old_data: Box<ColumnData>,
},
SetFrozenRowsCount {
sheet: u32,
new_value: i32,
old_value: i32,
},
SetFrozenColumnsCount {
sheet: u32,
new_value: i32,
old_value: i32,
},
DeleteSheet {
sheet: u32,
},
NewSheet {
index: u32,
name: String,
},
RenameSheet {
index: u32,
old_value: String,
new_value: String,
},
SetSheetColor {
index: u32,
old_value: String,
new_value: String,
},
SetShowGridLines {
sheet: u32,
old_value: bool,
new_value: bool,
}, // FIXME: we are missing SetViewDiffs
}
pub(crate) type DiffList = Vec<Diff>;
#[derive(Default)]
pub(crate) struct History {
pub(crate) undo_stack: Vec<DiffList>,
pub(crate) redo_stack: Vec<DiffList>,
}
impl History {
pub fn push(&mut self, diff_list: DiffList) {
self.undo_stack.push(diff_list);
self.redo_stack = vec![];
}
pub fn undo(&mut self) -> Option<Vec<Diff>> {
match self.undo_stack.pop() {
Some(diff_list) => {
self.redo_stack.push(diff_list.clone());
Some(diff_list)
}
None => None,
}
}
pub fn redo(&mut self) -> Option<Vec<Diff>> {
match self.redo_stack.pop() {
Some(diff_list) => {
self.undo_stack.push(diff_list.clone());
Some(diff_list)
}
None => None,
}
}
pub fn clear(&mut self) {
self.redo_stack = vec![];
self.undo_stack = vec![];
}
}
#[derive(Clone, Encode, Decode)]
pub enum DiffType {
Undo,
Redo,
}
#[derive(Clone, Encode, Decode)]
pub struct QueueDiffs {
pub r#type: DiffType,
pub list: DiffList,
}

View File

@@ -0,0 +1,12 @@
#![deny(missing_docs)]
mod common;
mod history;
mod ui;
pub use common::UserModel;
#[cfg(test)]
pub use ui::SelectedView;
pub use common::BorderArea;

671
base/src/user_model/ui.rs Normal file
View File

@@ -0,0 +1,671 @@
#![deny(missing_docs)]
use serde::{Deserialize, Serialize};
use crate::expressions::utils::{is_valid_column_number, is_valid_row};
use super::common::UserModel;
#[derive(Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq, Debug))]
pub struct SelectedView {
pub sheet: u32,
pub row: i32,
pub column: i32,
pub range: [i32; 4],
pub top_row: i32,
pub left_column: i32,
}
impl UserModel {
/// Returns the selected sheet index
pub fn get_selected_sheet(&self) -> u32 {
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
}
}
/// Returns the selected cell
pub fn get_selected_cell(&self) -> (u32, i32, i32) {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
};
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
if let Some(view) = worksheet.views.get(&self.model.view_id) {
return (sheet, view.row, view.column);
}
}
// return a safe default
(0, 1, 1)
}
/// Returns selected view
pub fn get_selected_view(&self) -> SelectedView {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
};
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
if let Some(view) = worksheet.views.get(&self.model.view_id) {
return SelectedView {
sheet,
row: view.row,
column: view.column,
range: view.range,
top_row: view.top_row,
left_column: view.left_column,
};
}
}
// return a safe default
SelectedView {
sheet: 0,
row: 1,
column: 1,
range: [1, 1, 1, 1],
top_row: 1,
left_column: 1,
}
}
/// Sets the the selected sheet
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<(), String> {
if self.model.workbook.worksheet(sheet).is_err() {
return Err(format!("Invalid worksheet index {}", sheet));
}
if let Some(view) = self.model.workbook.views.get_mut(&0) {
view.sheet = sheet;
}
Ok(())
}
/// Sets the selected cell
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<(), String> {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
};
if !is_valid_column_number(column) {
return Err(format!("Invalid column: '{column}'"));
}
if !is_valid_row(row) {
return Err(format!("Invalid row: '{row}'"));
}
if self.model.workbook.worksheet(sheet).is_err() {
return Err(format!("Invalid worksheet index {}", sheet));
}
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&0) {
view.row = row;
view.column = column;
view.range = [row, column, row, column];
}
}
Ok(())
}
/// Sets the selected range
pub fn set_selected_range(
&mut self,
start_row: i32,
start_column: i32,
end_row: i32,
end_column: i32,
) -> Result<(), String> {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
};
if !is_valid_column_number(start_column) {
return Err(format!("Invalid column: '{start_column}'"));
}
if !is_valid_row(start_row) {
return Err(format!("Invalid row: '{start_row}'"));
}
if !is_valid_column_number(end_column) {
return Err(format!("Invalid column: '{end_column}'"));
}
if !is_valid_row(end_row) {
return Err(format!("Invalid row: '{end_row}'"));
}
if self.model.workbook.worksheet(sheet).is_err() {
return Err(format!("Invalid worksheet index {}", sheet));
}
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&0) {
view.range = [start_row, start_column, end_row, end_column];
}
}
Ok(())
}
/// The selected range is expanded with the keyboard
pub fn on_expand_selected_range(&mut self, key: &str) -> Result<(), String> {
let (sheet, window_width, window_height) =
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
(
view.sheet,
view.window_width as f64,
view.window_height as f64,
)
} else {
return Ok(());
};
let (selected_row, selected_column, range, top_row, left_column) =
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
if let Some(view) = worksheet.views.get(&self.model.view_id) {
(
view.row,
view.column,
view.range,
view.top_row,
view.left_column,
)
} else {
return Ok(());
}
} else {
return Ok(());
};
let [row_start, column_start, row_end, column_end] = range;
match key {
"ArrowRight" => {
if selected_column > column_start {
let new_column = column_start + 1;
if !(is_valid_column_number(new_column)) {
return Ok(());
}
self.set_selected_range(row_start, new_column, row_end, column_end)?;
} else {
let new_column = column_end + 1;
if !is_valid_column_number(new_column) {
return Ok(());
}
// if the column is not fully visible we 'scroll' right until it is
let mut width = 0.0;
let mut c = left_column;
while c <= new_column {
width += self.model.get_column_width(sheet, c)?;
c += 1;
}
if width > window_width {
self.set_top_left_visible_cell(top_row, left_column + 1)?;
}
self.set_selected_range(row_start, column_start, row_end, column_end + 1)?;
}
}
"ArrowLeft" => {
if selected_column < column_end {
let new_column = column_end - 1;
if !is_valid_column_number(new_column) {
return Ok(());
}
if new_column < left_column {
self.set_top_left_visible_cell(top_row, new_column)?;
}
self.set_selected_range(row_start, column_start, row_end, new_column)?;
} else {
let new_column = column_start - 1;
if !is_valid_column_number(new_column) {
return Ok(());
}
if new_column < left_column {
self.set_top_left_visible_cell(top_row, new_column)?;
}
self.set_selected_range(row_start, new_column, row_end, column_end)?;
}
}
"ArrowUp" => {
if selected_row < row_end {
let new_row = row_end - 1;
if !is_valid_row(new_row) {
return Ok(());
}
self.set_selected_range(row_start, column_start, new_row, column_end)?;
} else {
let new_row = row_start - 1;
if !is_valid_row(new_row) {
return Ok(());
}
if new_row < top_row {
self.set_top_left_visible_cell(new_row, left_column)?;
}
self.set_selected_range(new_row, column_start, row_end, column_end)?;
}
}
"ArrowDown" => {
if selected_row > row_start {
let new_row = row_start + 1;
if !is_valid_row(new_row) {
return Ok(());
}
self.set_selected_range(new_row, column_start, row_end, column_end)?;
} else {
let new_row = row_end + 1;
if !is_valid_row(new_row) {
return Ok(());
}
let mut height = 0.0;
let mut r = top_row;
while r <= new_row + 1 {
height += self.model.get_row_height(sheet, r)?;
r += 1;
}
if height >= window_height {
self.set_top_left_visible_cell(top_row + 1, left_column)?;
}
self.set_selected_range(row_start, column_start, new_row, column_end)?;
}
}
_ => {}
}
Ok(())
}
/// Sets the value of the first visible cell
pub fn set_top_left_visible_cell(
&mut self,
top_row: i32,
left_column: i32,
) -> Result<(), String> {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
0
};
if !is_valid_column_number(left_column) {
return Err(format!("Invalid column: '{left_column}'"));
}
if !is_valid_row(top_row) {
return Err(format!("Invalid row: '{top_row}'"));
}
if self.model.workbook.worksheet(sheet).is_err() {
return Err(format!("Invalid worksheet index {}", sheet));
}
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&0) {
view.top_row = top_row;
view.left_column = left_column;
}
}
Ok(())
}
/// Sets the width of the window
pub fn set_window_width(&mut self, window_width: f64) {
if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
view.window_width = window_width as i64;
};
}
/// Gets the width of the window
pub fn get_window_width(&mut self) -> Result<i64, String> {
if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
return Ok(view.window_width);
};
Err("View not found".to_string())
}
/// Sets the height of the window
pub fn set_window_height(&mut self, window_height: f64) {
if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
view.window_height = window_height as i64;
};
}
/// Gets the height of the window
pub fn get_window_height(&mut self) -> Result<i64, String> {
if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
return Ok(view.window_height);
};
Err("View not found".to_string())
}
/// User presses right arrow
pub fn on_arrow_right(&mut self) -> Result<(), String> {
let (sheet, window_width) =
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
(view.sheet, view.window_width)
} else {
return Err("View not found".to_string());
};
let worksheet = match self.model.workbook.worksheet(sheet) {
Ok(s) => s,
Err(_) => return Err("Worksheet not found".to_string()),
};
let view = match worksheet.views.get(&self.model.view_id) {
Some(s) => s,
None => return Err("View not found".to_string()),
};
let new_column = view.column + 1;
if !is_valid_column_number(new_column) {
return Ok(());
}
// if the column is not fully visible we 'scroll' right until it is
let mut width = 0.0;
let mut column = view.left_column;
while column <= new_column {
width += self.model.get_column_width(sheet, column)?;
column += 1;
}
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
view.column = new_column;
view.range = [view.row, new_column, view.row, new_column];
if width > window_width as f64 {
view.left_column += 1;
}
}
}
Ok(())
}
/// User presses left arrow
pub fn on_arrow_left(&mut self) -> Result<(), String> {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
return Err("View not found".to_string());
};
let worksheet = match self.model.workbook.worksheet(sheet) {
Ok(s) => s,
Err(_) => return Err("Worksheet not found".to_string()),
};
let view = match worksheet.views.get(&self.model.view_id) {
Some(s) => s,
None => return Err("View not found".to_string()),
};
let new_column = view.column - 1;
if !is_valid_column_number(new_column) {
return Ok(());
}
// if the column is not fully visible we 'scroll' right until it is
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
view.column = new_column;
view.range = [view.row, new_column, view.row, new_column];
if new_column < view.left_column {
view.left_column = new_column;
}
}
}
Ok(())
}
/// User presses up arrow key
pub fn on_arrow_up(&mut self) -> Result<(), String> {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
return Err("View not found".to_string());
};
let worksheet = match self.model.workbook.worksheet(sheet) {
Ok(s) => s,
Err(_) => return Err("Worksheet not found".to_string()),
};
let view = match worksheet.views.get(&self.model.view_id) {
Some(s) => s,
None => return Err("View not found".to_string()),
};
let new_row = view.row - 1;
if !is_valid_row(new_row) {
return Ok(());
}
// if the column is not fully visible we 'scroll' right until it is
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
view.row = new_row;
view.range = [new_row, view.column, new_row, view.column];
if new_row < view.top_row {
view.top_row = new_row;
}
}
}
Ok(())
}
/// User presses down arrow key
pub fn on_arrow_down(&mut self) -> Result<(), String> {
let (sheet, window_height) =
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
(view.sheet, view.window_height)
} else {
return Err("View not found".to_string());
};
let worksheet = match self.model.workbook.worksheet(sheet) {
Ok(s) => s,
Err(_) => return Err("Worksheet not found".to_string()),
};
let view = match worksheet.views.get(&self.model.view_id) {
Some(s) => s,
None => return Err("View not found".to_string()),
};
let new_row = view.row + 1;
if !is_valid_row(new_row) {
return Ok(());
}
// if the row is not fully visible we 'scroll' down until it is
let mut height = 0.0;
let mut row = view.top_row;
while row <= new_row + 1 {
height += self.model.get_row_height(sheet, row)?;
row += 1;
}
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
view.row = new_row;
view.range = [new_row, view.column, new_row, view.column];
if height > window_height as f64 {
view.top_row += 1;
}
}
}
Ok(())
}
// TODO: This function should be memoized
/// Returns the x-coordinate of the cell in the top left corner
pub fn get_scroll_x(&self) -> Result<f64, String> {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
return Err("View not found".to_string());
};
let worksheet = match self.model.workbook.worksheet(sheet) {
Ok(s) => s,
Err(_) => return Err("Worksheet not found".to_string()),
};
let view = match worksheet.views.get(&self.model.view_id) {
Some(s) => s,
None => return Err("View not found".to_string()),
};
let mut scroll_x = 0.0;
for column in 1..view.left_column {
scroll_x += self.model.get_column_width(sheet, column)?;
}
Ok(scroll_x)
}
// TODO: This function should be memoized
/// Returns the y-coordinate of the cell in the top left corner
pub fn get_scroll_y(&self) -> Result<f64, String> {
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
view.sheet
} else {
return Err("View not found".to_string());
};
let worksheet = match self.model.workbook.worksheet(sheet) {
Ok(s) => s,
Err(_) => return Err("Worksheet not found".to_string()),
};
let view = match worksheet.views.get(&self.model.view_id) {
Some(s) => s,
None => return Err("View not found".to_string()),
};
let mut scroll_y = 0.0;
for row in 1..view.top_row {
scroll_y += self.model.get_row_height(sheet, row)?;
}
Ok(scroll_y)
}
/// User presses page down
pub fn on_page_down(&mut self) -> Result<(), String> {
let (sheet, window_height) =
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
(view.sheet, view.window_height)
} else {
return Err("View not found".to_string());
};
let worksheet = match self.model.workbook.worksheet(sheet) {
Ok(s) => s,
Err(_) => return Err("Worksheet not found".to_string()),
};
let view = match worksheet.views.get(&self.model.view_id) {
Some(s) => s,
None => return Err("View not found".to_string()),
};
let mut height = 0.0;
let mut last_row = view.top_row;
while height <= window_height as f64 {
height += self.model.get_row_height(sheet, last_row)?;
last_row += 1;
}
if !is_valid_row(last_row) {
return Ok(());
}
let row_delta = view.row - view.top_row;
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
view.top_row = last_row;
view.row = view.top_row + row_delta;
view.range = [view.row, view.column, view.row, view.column];
}
}
Ok(())
}
/// On page up
pub fn on_page_up(&mut self) -> Result<(), String> {
let (sheet, window_height) =
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
(view.sheet, view.window_height)
} else {
return Err("View not found".to_string());
};
let worksheet = match self.model.workbook.worksheet(sheet) {
Ok(s) => s,
Err(_) => return Err("Worksheet not found".to_string()),
};
let view = match worksheet.views.get(&self.model.view_id) {
Some(s) => s,
None => return Err("View not found".to_string()),
};
let mut height = 0.0;
let mut last_row = view.top_row;
while height <= window_height as f64 && last_row > 1 {
height += self.model.get_row_height(sheet, last_row)?;
last_row -= 1;
}
let row_delta = view.row - view.top_row;
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
view.top_row = last_row;
view.row = view.top_row + row_delta;
view.range = [view.row, view.column, view.row, view.column];
}
}
Ok(())
}
/// We extend the selection to cell (target_row, target_column)
pub fn on_area_selecting(&mut self, target_row: i32, target_column: i32) -> Result<(), String> {
let (sheet, window_width, window_height) =
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
(
view.sheet,
view.window_width as f64,
view.window_height as f64,
)
} else {
return Ok(());
};
let (selected_row, selected_column, range, top_row, left_column) =
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
if let Some(view) = worksheet.views.get(&self.model.view_id) {
(
view.row,
view.column,
view.range,
view.top_row,
view.left_column,
)
} else {
return Ok(());
}
} else {
return Ok(());
};
let [row_start, column_start, _row_end, _column_end] = range;
let mut new_left_column = left_column;
if target_column >= selected_column {
let mut width = 0.0;
let mut column = left_column;
while column <= target_column {
width += self.model.get_column_width(sheet, column)?;
column += 1;
}
while width > window_width {
width -= self.model.get_column_width(sheet, new_left_column)?;
new_left_column += 1;
}
} else if target_column < new_left_column {
new_left_column = target_column;
}
let mut new_top_row = top_row;
if target_row >= selected_row {
let mut height = 0.0;
let mut row = top_row;
while row <= target_row {
height += self.model.get_row_height(sheet, row)?;
row += 1;
}
while height > window_height {
height -= self.model.get_row_height(sheet, new_top_row)?;
new_top_row += 1;
}
} else if target_row < new_top_row {
new_top_row = target_row;
}
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
view.range = [row_start, column_start, target_row, target_column];
if new_top_row != top_row {
view.top_row = new_top_row;
}
if new_left_column != left_column {
view.left_column = new_left_column;
}
}
}
Ok(())
}
}