1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 15:57:45 +00:00

Spreadsheet: struct Cell => class Cell

Hide private members, and make the odd update() -> sheet->update(cell)
-> update(Badge<Sheet>) -> update_data() less odd by removing the
update(Badge<Sheet>) step.
This commit is contained in:
AnotherTest 2020-12-20 12:58:14 +03:30 committed by Andreas Kling
parent 28428beb5c
commit f1f9fd1c60
8 changed files with 110 additions and 93 deletions

View file

@ -33,32 +33,32 @@ namespace Spreadsheet {
void Cell::set_data(String new_data) void Cell::set_data(String new_data)
{ {
if (data == new_data) if (m_data == new_data)
return; return;
if (new_data.starts_with("=")) { if (new_data.starts_with("=")) {
new_data = new_data.substring(1, new_data.length() - 1); new_data = new_data.substring(1, new_data.length() - 1);
kind = Formula; m_kind = Formula;
} else { } else {
kind = LiteralString; m_kind = LiteralString;
} }
data = move(new_data); m_data = move(new_data);
dirty = true; m_dirty = true;
evaluated_externally = false; m_evaluated_externally = false;
} }
void Cell::set_data(JS::Value new_data) void Cell::set_data(JS::Value new_data)
{ {
dirty = true; m_dirty = true;
evaluated_externally = true; m_evaluated_externally = true;
StringBuilder builder; StringBuilder builder;
builder.append(new_data.to_string_without_side_effects()); builder.append(new_data.to_string_without_side_effects());
data = builder.build(); m_data = builder.build();
evaluated_data = move(new_data); m_evaluated_data = move(new_data);
} }
void Cell::set_type(const CellType* type) void Cell::set_type(const CellType* type)
@ -86,8 +86,8 @@ const CellType& Cell::type() const
if (m_type) if (m_type)
return *m_type; return *m_type;
if (kind == LiteralString) { if (m_kind == LiteralString) {
if (data.to_int().has_value()) if (m_data.to_int().has_value())
return *CellType::get_by_name("Numeric"); return *CellType::get_by_name("Numeric");
} }
@ -104,22 +104,22 @@ JS::Value Cell::typed_js_data() const
return type().js_value(const_cast<Cell&>(*this), m_type_metadata); return type().js_value(const_cast<Cell&>(*this), m_type_metadata);
} }
void Cell::update_data() void Cell::update_data(Badge<Sheet>)
{ {
TemporaryChange cell_change { sheet->current_evaluated_cell(), this }; TemporaryChange cell_change { m_sheet->current_evaluated_cell(), this };
if (!dirty) if (!m_dirty)
return; return;
if (dirty) { if (m_dirty) {
dirty = false; m_dirty = false;
if (kind == Formula) { if (m_kind == Formula) {
if (!evaluated_externally) if (!m_evaluated_externally)
evaluated_data = sheet->evaluate(data, this); m_evaluated_data = m_sheet->evaluate(m_data, this);
} }
for (auto& ref : referencing_cells) { for (auto& ref : m_referencing_cells) {
if (ref) { if (ref) {
ref->dirty = true; ref->m_dirty = true;
ref->update(); ref->update();
} }
} }
@ -134,7 +134,7 @@ void Cell::update_data()
builder.append("return ("); builder.append("return (");
builder.append(fmt.condition); builder.append(fmt.condition);
builder.append(')'); builder.append(')');
auto value = sheet->evaluate(builder.string_view(), this); auto value = m_sheet->evaluate(builder.string_view(), this);
if (value.to_boolean()) { if (value.to_boolean()) {
if (fmt.background_color.has_value()) if (fmt.background_color.has_value())
m_evaluated_formats.background_color = fmt.background_color; m_evaluated_formats.background_color = fmt.background_color;
@ -147,26 +147,26 @@ void Cell::update_data()
void Cell::update() void Cell::update()
{ {
sheet->update(*this); m_sheet->update(*this);
} }
JS::Value Cell::js_data() JS::Value Cell::js_data()
{ {
if (dirty) if (m_dirty)
update(); update();
if (kind == Formula) if (m_kind == Formula)
return evaluated_data; return m_evaluated_data;
return JS::js_string(sheet->interpreter().heap(), data); return JS::js_string(m_sheet->interpreter().heap(), m_data);
} }
String Cell::source() const String Cell::source() const
{ {
StringBuilder builder; StringBuilder builder;
if (kind == Formula) if (m_kind == Formula)
builder.append('='); builder.append('=');
builder.append(data); builder.append(m_data);
return builder.to_string(); return builder.to_string();
} }
@ -176,10 +176,23 @@ void Cell::reference_from(Cell* other)
if (!other || other == this) if (!other || other == this)
return; return;
if (!referencing_cells.find([other](auto& ptr) { return ptr.ptr() == other; }).is_end()) if (!m_referencing_cells.find([other](auto& ptr) { return ptr.ptr() == other; }).is_end())
return; return;
referencing_cells.append(other->make_weak_ptr()); m_referencing_cells.append(other->make_weak_ptr());
}
void Cell::copy_from(const Cell& other)
{
m_dirty = true;
m_evaluated_externally = other.m_evaluated_externally;
m_data = other.m_data;
m_evaluated_data = other.m_evaluated_data;
m_kind = other.m_kind;
m_type = other.m_type;
m_type_metadata = other.m_type_metadata;
m_conditional_formats = other.m_conditional_formats;
m_evaluated_formats = other.m_evaluated_formats;
} }
} }

View file

@ -38,21 +38,26 @@
namespace Spreadsheet { namespace Spreadsheet {
struct Cell : public Weakable<Cell> { struct Cell : public Weakable<Cell> {
enum Kind {
LiteralString,
Formula,
};
Cell(String data, Position position, WeakPtr<Sheet> sheet) Cell(String data, Position position, WeakPtr<Sheet> sheet)
: dirty(false) : m_dirty(false)
, data(move(data)) , m_data(move(data))
, kind(LiteralString) , m_kind(LiteralString)
, sheet(sheet) , m_sheet(sheet)
, m_position(move(position)) , m_position(move(position))
{ {
} }
Cell(String source, JS::Value&& cell_value, Position position, WeakPtr<Sheet> sheet) Cell(String source, JS::Value&& cell_value, Position position, WeakPtr<Sheet> sheet)
: dirty(false) : m_dirty(false)
, data(move(source)) , m_data(move(source))
, evaluated_data(move(cell_value)) , m_evaluated_data(move(cell_value))
, kind(Formula) , m_kind(Formula)
, sheet(sheet) , m_sheet(sheet)
, m_position(move(position)) , m_position(move(position))
{ {
} }
@ -61,6 +66,12 @@ struct Cell : public Weakable<Cell> {
void set_data(String new_data); void set_data(String new_data);
void set_data(JS::Value new_data); void set_data(JS::Value new_data);
bool dirty() const { return m_dirty; }
const String& data() const { return m_data; }
const JS::Value& evaluated_data() const { return m_evaluated_data; }
Kind kind() const { return m_kind; }
const Vector<WeakPtr<Cell>>& referencing_cells() const { return m_referencing_cells; }
void set_type(const StringView& name); void set_type(const StringView& name);
void set_type(const CellType*); void set_type(const CellType*);
@ -69,15 +80,16 @@ struct Cell : public Weakable<Cell> {
const Position& position() const { return m_position; } const Position& position() const { return m_position; }
void set_position(Position position, Badge<Sheet>) void set_position(Position position, Badge<Sheet>)
{ {
dirty = true; m_dirty = true;
m_position = move(position); m_position = move(position);
} }
const Format& evaluated_formats() const { return m_evaluated_formats; } const Format& evaluated_formats() const { return m_evaluated_formats; }
Format& evaluated_formats() { return m_evaluated_formats; }
const Vector<ConditionalFormat>& conditional_formats() const { return m_conditional_formats; } const Vector<ConditionalFormat>& conditional_formats() const { return m_conditional_formats; }
void set_conditional_formats(Vector<ConditionalFormat>&& fmts) void set_conditional_formats(Vector<ConditionalFormat>&& fmts)
{ {
dirty = true; m_dirty = true;
m_conditional_formats = move(fmts); m_conditional_formats = move(fmts);
} }
@ -92,30 +104,28 @@ struct Cell : public Weakable<Cell> {
JS::Value js_data(); JS::Value js_data();
void update(Badge<Sheet>) { update_data(); }
void update(); void update();
void update_data(Badge<Sheet>);
enum Kind { const Sheet& sheet() const { return *m_sheet; }
LiteralString, Sheet& sheet() { return *m_sheet; }
Formula,
};
bool dirty { false }; void copy_from(const Cell&);
bool evaluated_externally { false };
String data; private:
JS::Value evaluated_data; bool m_dirty { false };
Kind kind { LiteralString }; bool m_evaluated_externally { false };
WeakPtr<Sheet> sheet; String m_data;
Vector<WeakPtr<Cell>> referencing_cells; JS::Value m_evaluated_data;
Kind m_kind { LiteralString };
WeakPtr<Sheet> m_sheet;
Vector<WeakPtr<Cell>> m_referencing_cells;
const CellType* m_type { nullptr }; const CellType* m_type { nullptr };
CellTypeMetadata m_type_metadata; CellTypeMetadata m_type_metadata;
Position m_position; Position m_position;
Vector<ConditionalFormat> m_conditional_formats; Vector<ConditionalFormat> m_conditional_formats;
Format m_evaluated_formats; Format m_evaluated_formats;
private:
void update_data();
}; };
} }

View file

@ -43,7 +43,7 @@ DateCell::~DateCell()
String DateCell::display(Cell& cell, const CellTypeMetadata& metadata) const String DateCell::display(Cell& cell, const CellTypeMetadata& metadata) const
{ {
auto timestamp = js_value(cell, metadata); auto timestamp = js_value(cell, metadata);
auto string = Core::DateTime::from_timestamp(timestamp.to_i32(cell.sheet->global_object())).to_string(metadata.format.is_empty() ? "%Y-%m-%d %H:%M:%S" : metadata.format.characters()); auto string = Core::DateTime::from_timestamp(timestamp.to_i32(cell.sheet().global_object())).to_string(metadata.format.is_empty() ? "%Y-%m-%d %H:%M:%S" : metadata.format.characters());
if (metadata.length >= 0) if (metadata.length >= 0)
return string.substring(0, metadata.length); return string.substring(0, metadata.length);
@ -53,7 +53,7 @@ String DateCell::display(Cell& cell, const CellTypeMetadata& metadata) const
JS::Value DateCell::js_value(Cell& cell, const CellTypeMetadata&) const JS::Value DateCell::js_value(Cell& cell, const CellTypeMetadata&) const
{ {
auto value = cell.js_data().to_double(cell.sheet->global_object()); auto value = cell.js_data().to_double(cell.sheet().global_object());
return JS::Value(value / 1000); // Turn it to seconds return JS::Value(value / 1000); // Turn it to seconds
} }

View file

@ -47,7 +47,7 @@ String NumericCell::display(Cell& cell, const CellTypeMetadata& metadata) const
if (metadata.format.is_empty()) if (metadata.format.is_empty())
string = value.to_string_without_side_effects(); string = value.to_string_without_side_effects();
else else
string = format_double(metadata.format.characters(), value.to_double(cell.sheet->global_object())); string = format_double(metadata.format.characters(), value.to_double(cell.sheet().global_object()));
if (metadata.length >= 0) if (metadata.length >= 0)
return string.substring(0, metadata.length); return string.substring(0, metadata.length);
@ -57,7 +57,7 @@ String NumericCell::display(Cell& cell, const CellTypeMetadata& metadata) const
JS::Value NumericCell::js_value(Cell& cell, const CellTypeMetadata&) const JS::Value NumericCell::js_value(Cell& cell, const CellTypeMetadata&) const
{ {
return cell.js_data().to_number(cell.sheet->global_object()); return cell.js_data().to_number(cell.sheet().global_object());
} }
} }

View file

@ -51,7 +51,7 @@ String StringCell::display(Cell& cell, const CellTypeMetadata& metadata) const
JS::Value StringCell::js_value(Cell& cell, const CellTypeMetadata& metadata) const JS::Value StringCell::js_value(Cell& cell, const CellTypeMetadata& metadata) const
{ {
auto string = display(cell, metadata); auto string = display(cell, metadata);
return JS::js_string(cell.sheet->interpreter().heap(), string); return JS::js_string(cell.sheet().interpreter().heap(), string);
} }
} }

View file

@ -60,8 +60,8 @@ Sheet::Sheet(Workbook& workbook)
: m_workbook(workbook) : m_workbook(workbook)
{ {
m_global_object = m_workbook.interpreter().heap().allocate_without_global_object<SheetGlobalObject>(*this); m_global_object = m_workbook.interpreter().heap().allocate_without_global_object<SheetGlobalObject>(*this);
m_global_object->set_prototype(&m_workbook.global_object());
m_global_object->initialize(); m_global_object->initialize();
m_global_object->put("workbook", m_workbook.workbook_object());
m_global_object->put("thisSheet", m_global_object); // Self-reference is unfortunate, but required. m_global_object->put("thisSheet", m_global_object); // Self-reference is unfortunate, but required.
// Sadly, these have to be evaluated once per sheet. // Sadly, these have to be evaluated once per sheet.
@ -160,26 +160,23 @@ void Sheet::update()
for (auto& it : m_cells) for (auto& it : m_cells)
cells_copy.append(it.value); cells_copy.append(it.value);
for (auto& cell : cells_copy) { for (auto& cell : cells_copy)
if (has_been_visited(cell)) update(*cell);
continue;
m_visited_cells_in_update.set(cell);
if (cell->dirty) {
// Re-evaluate the cell value, if any.
cell->update({});
}
}
m_visited_cells_in_update.clear(); m_visited_cells_in_update.clear();
} }
void Sheet::update(Cell& cell) void Sheet::update(Cell& cell)
{ {
if (has_been_visited(&cell)) if (cell.dirty()) {
return; if (has_been_visited(&cell)) {
// This may be part of an cyclic reference chain
m_visited_cells_in_update.set(&cell); // just break the chain, but leave the cell dirty.
cell.update({}); return;
}
m_visited_cells_in_update.set(&cell);
cell.update_data({});
}
} }
JS::Value Sheet::evaluate(const StringView& source, Cell* on_behalf_of) JS::Value Sheet::evaluate(const StringView& source, Cell* on_behalf_of)
@ -323,10 +320,7 @@ void Sheet::copy_cells(Vector<Position> from, Vector<Position> to, Optional<Posi
return; return;
} }
auto ref_cells = target_cell.referencing_cells; target_cell.copy_from(*source_cell);
target_cell = *source_cell;
target_cell.dirty = true;
target_cell.referencing_cells = move(ref_cells);
}; };
if (from.size() == to.size()) { if (from.size() == to.size()) {
@ -467,7 +461,7 @@ RefPtr<Sheet> Sheet::from_json(const JsonObject& object, Workbook& workbook)
auto evaluated_format = obj.get("evaluated_formats"); auto evaluated_format = obj.get("evaluated_formats");
if (evaluated_format.is_object()) { if (evaluated_format.is_object()) {
auto& evaluated_format_obj = evaluated_format.as_object(); auto& evaluated_format_obj = evaluated_format.as_object();
auto& evaluated_fmts = cell->m_evaluated_formats; auto& evaluated_fmts = cell->evaluated_formats();
read_format(evaluated_fmts, evaluated_format_obj); read_format(evaluated_fmts, evaluated_format_obj);
} }
@ -535,14 +529,14 @@ JsonObject Sheet::to_json() const
auto key = builder.to_string(); auto key = builder.to_string();
JsonObject data; JsonObject data;
data.set("kind", it.value->kind == Cell::Kind::Formula ? "Formula" : "LiteralString"); data.set("kind", it.value->kind() == Cell::Kind::Formula ? "Formula" : "LiteralString");
if (it.value->kind == Cell::Formula) { if (it.value->kind() == Cell::Formula) {
data.set("source", it.value->data); data.set("source", it.value->data());
auto json = interpreter().global_object().get("JSON"); auto json = interpreter().global_object().get("JSON");
auto stringified = interpreter().vm().call(json.as_object().get("stringify").as_function(), json, it.value->evaluated_data); auto stringified = interpreter().vm().call(json.as_object().get("stringify").as_function(), json, it.value->evaluated_data());
data.set("value", stringified.to_string_without_side_effects()); data.set("value", stringified.to_string_without_side_effects());
} else { } else {
data.set("value", it.value->data); data.set("value", it.value->data());
} }
// Set type & meta // Set type & meta

View file

@ -57,8 +57,8 @@ GUI::Variant SheetModel::data(const GUI::ModelIndex& index, GUI::ModelRole role)
if (!cell) if (!cell)
return String::empty(); return String::empty();
if (cell->kind == Spreadsheet::Cell::Formula) { if (cell->kind() == Spreadsheet::Cell::Formula) {
if (auto object = as_error(cell->evaluated_data)) { if (auto object = as_error(cell->evaluated_data())) {
StringBuilder builder; StringBuilder builder;
auto error = object->get("message").to_string_without_side_effects(); auto error = object->get("message").to_string_without_side_effects();
builder.append("Error: "); builder.append("Error: ");
@ -86,8 +86,8 @@ GUI::Variant SheetModel::data(const GUI::ModelIndex& index, GUI::ModelRole role)
if (!cell) if (!cell)
return {}; return {};
if (cell->kind == Spreadsheet::Cell::Formula) { if (cell->kind() == Spreadsheet::Cell::Formula) {
if (as_error(cell->evaluated_data)) if (as_error(cell->evaluated_data()))
return Color(Color::Red); return Color(Color::Red);
} }

View file

@ -181,7 +181,7 @@ int main(int argc, char* argv[])
if (!first) if (!first)
text_builder.append('\t'); text_builder.append('\t');
if (cell_data) if (cell_data)
text_builder.append(cell_data->data); text_builder.append(cell_data->data());
first = false; first = false;
} }
HashMap<String, String> metadata; HashMap<String, String> metadata;