Skip to main content

ironcalc_base/user_model/
links.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3
4use crate::links::{CellLinkView, THEME_COLOR_HYPERLINK};
5use crate::types::{Cell, Color, Link};
6
7use super::{common::UserModel, history::Diff};
8
9impl UserModel<'_> {
10    /// Returns the link attached to cell (`row`, `column`) or `None` if there isn't one.
11    pub fn get_cell_link(&self, sheet: u32, row: i32, column: i32) -> Result<Option<Link>, String> {
12        self.model.get_cell_link(sheet, row, column)
13    }
14
15    /// Returns all the links in the worksheet, keyed by (row, column).
16    pub fn get_links(&self, sheet: u32) -> Result<&HashMap<(i32, i32), Link>, String> {
17        self.model.get_links(sheet)
18    }
19
20    /// Returns all the links in the worksheet as a list sorted by (row, column).
21    pub fn get_links_list(&self, sheet: u32) -> Result<Vec<CellLinkView>, String> {
22        self.model.get_links_list(sheet)
23    }
24
25    /// Attaches `link` to cell (`row`, `column`), replacing the existing link if there
26    /// was one.
27    ///
28    /// If `label` is given it becomes the content of the cell (the displayed text of
29    /// the link). When the cell did not have a link before, the link style (underline
30    /// and the theme hyperlink color) is applied to the cell. The style is ordinary
31    /// cell formatting: it can be changed afterwards and deleting the link does not
32    /// remove it.
33    ///
34    /// The whole operation is a single entry in the undo/redo history.
35    pub fn set_cell_link(
36        &mut self,
37        sheet: u32,
38        row: i32,
39        column: i32,
40        link: Link,
41        label: Option<&str>,
42    ) -> Result<(), String> {
43        let old_link = self.model.get_cell_link(sheet, row, column)?;
44        let is_new_link = old_link.is_none();
45        let mut diff_list = Vec::new();
46        let mut needs_evaluation = false;
47
48        if old_link.as_ref() != Some(&link) {
49            self.model.set_cell_link(sheet, row, column, link.clone())?;
50            diff_list.push(Diff::SetCellLink {
51                sheet,
52                row,
53                column,
54                old_value: Box::new(old_link),
55                new_value: Box::new(Some(link)),
56            });
57        }
58
59        if let Some(label) = label {
60            if label != self.model.get_formatted_cell_value(sheet, row, column)? {
61                let old_value = self
62                    .model
63                    .workbook
64                    .worksheet(sheet)?
65                    .cell(row, column)
66                    .map(Cow::into_owned);
67                // If it is a spill cell we want to save the old value as None, because
68                // the value of a spill cell is determined by the anchor cell
69                let old_value = if matches!(old_value, Some(Cell::SpillCell { .. })) {
70                    None
71                } else {
72                    old_value
73                };
74                self.model
75                    .set_user_input(sheet, row, column, label.to_string())?;
76                needs_evaluation = true;
77                diff_list.push(Diff::SetCellValue {
78                    sheet,
79                    row,
80                    column,
81                    new_value: label.to_string(),
82                    old_value: Box::new(old_value),
83                });
84            }
85        }
86
87        if is_new_link {
88            let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
89            let mut style = self.model.get_style_for_cell(sheet, row, column)?;
90            style.font.u = true;
91            style.font.color = Color::Theme(THEME_COLOR_HYPERLINK, 0.0);
92            self.model.set_cell_style(sheet, row, column, &style)?;
93            diff_list.push(Diff::SetCellStyle {
94                sheet,
95                row,
96                column,
97                old_value: Box::new(old_style),
98                new_value: Box::new(style),
99            });
100        }
101
102        if diff_list.is_empty() {
103            // no-op, don't pollute the undo history
104            return Ok(());
105        }
106        self.push_diff_list(diff_list);
107        if needs_evaluation {
108            self.evaluate_if_not_paused();
109        }
110        Ok(())
111    }
112
113    /// Removes the link attached to cell (`row`, `column`). It is not an error if the
114    /// cell has no link. The cell content and the cell style are left untouched.
115    pub fn delete_cell_link(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
116        let old_value = self.model.get_cell_link(sheet, row, column)?;
117        if old_value.is_none() {
118            return Ok(());
119        }
120        self.model.delete_cell_link(sheet, row, column)?;
121        self.push_diff_list(vec![Diff::SetCellLink {
122            sheet,
123            row,
124            column,
125            old_value: Box::new(old_value),
126            new_value: Box::new(None),
127        }]);
128        Ok(())
129    }
130}