diff options
author | AnotherTest <ali.mpfard@gmail.com> | 2020-08-26 07:32:38 +0430 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-08-27 10:27:20 +0200 |
commit | cb7fe4fe7c50dc3c9da8f129aa60c74e93ef1304 (patch) | |
tree | e22ee7d77c29e0f412c43555a422b438de9d5806 /Applications/Spreadsheet | |
parent | e1f5f709eeecdd9aea0714b924f6bbb2096abdbf (diff) | |
download | serenity-cb7fe4fe7c50dc3c9da8f129aa60c74e93ef1304.zip |
Spreadsheet: Add support for multiple sheets
This also refactors the js integration stuff to allow sheets to
reference each other safely.
Diffstat (limited to 'Applications/Spreadsheet')
-rw-r--r-- | Applications/Spreadsheet/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Applications/Spreadsheet/JSIntegration.cpp | 157 | ||||
-rw-r--r-- | Applications/Spreadsheet/JSIntegration.h | 71 | ||||
-rw-r--r-- | Applications/Spreadsheet/Spreadsheet.cpp | 160 | ||||
-rw-r--r-- | Applications/Spreadsheet/Spreadsheet.h | 18 | ||||
-rw-r--r-- | Applications/Spreadsheet/SpreadsheetWidget.cpp | 21 | ||||
-rw-r--r-- | Applications/Spreadsheet/SpreadsheetWidget.h | 3 | ||||
-rw-r--r-- | Applications/Spreadsheet/Workbook.cpp | 13 | ||||
-rw-r--r-- | Applications/Spreadsheet/Workbook.h | 19 | ||||
-rw-r--r-- | Applications/Spreadsheet/main.cpp | 3 |
10 files changed, 346 insertions, 120 deletions
diff --git a/Applications/Spreadsheet/CMakeLists.txt b/Applications/Spreadsheet/CMakeLists.txt index 614fbc4ae0..9eac41dc0f 100644 --- a/Applications/Spreadsheet/CMakeLists.txt +++ b/Applications/Spreadsheet/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES CellSyntaxHighlighter.cpp HelpWindow.cpp + JSIntegration.cpp Spreadsheet.cpp SpreadsheetModel.cpp SpreadsheetView.cpp diff --git a/Applications/Spreadsheet/JSIntegration.cpp b/Applications/Spreadsheet/JSIntegration.cpp new file mode 100644 index 0000000000..830b664f6f --- /dev/null +++ b/Applications/Spreadsheet/JSIntegration.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "JSIntegration.h" +#include "Spreadsheet.h" +#include "Workbook.h" +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/Value.h> + +namespace Spreadsheet { + +SheetGlobalObject::SheetGlobalObject(Sheet& sheet) + : m_sheet(sheet) +{ +} + +SheetGlobalObject::~SheetGlobalObject() +{ +} + +JS::Value SheetGlobalObject::get(const JS::PropertyName& name, JS::Value receiver) const +{ + if (name.is_string()) { + if (auto pos = Sheet::parse_cell_name(name.as_string()); pos.has_value()) { + auto& cell = m_sheet.ensure(pos.value()); + cell.reference_from(m_sheet.current_evaluated_cell()); + return cell.js_data(); + } + } + + return GlobalObject::get(name, receiver); +} + +bool SheetGlobalObject::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) +{ + if (name.is_string()) { + if (auto pos = Sheet::parse_cell_name(name.as_string()); pos.has_value()) { + auto& cell = m_sheet.ensure(pos.value()); + if (auto current = m_sheet.current_evaluated_cell()) + current->reference_from(&cell); + + cell.set_data(value); // FIXME: This produces un-savable state! + return true; + } + } + + return GlobalObject::put(name, value, receiver); +} + +void SheetGlobalObject::initialize() +{ + GlobalObject::initialize(); + define_native_function("parse_cell_name", parse_cell_name, 1); +} + +JS_DEFINE_NATIVE_FUNCTION(SheetGlobalObject::parse_cell_name) +{ + if (interpreter.argument_count() != 1) { + interpreter.throw_exception<JS::TypeError>("Expected exactly one argument to parse_cell_name()"); + return {}; + } + auto name_value = interpreter.argument(0); + if (!name_value.is_string()) { + interpreter.throw_exception<JS::TypeError>("Expected a String argument to parse_cell_name()"); + return {}; + } + auto position = Sheet::parse_cell_name(name_value.as_string().string()); + if (!position.has_value()) + return JS::js_undefined(); + + auto object = JS::Object::create_empty(interpreter.global_object()); + object->put("column", JS::js_string(interpreter, position.value().column)); + object->put("row", JS::Value((unsigned)position.value().row)); + + return object; +} + +WorkbookObject::WorkbookObject(Workbook& workbook) + : JS::Object(*JS::Object::create_empty(workbook.global_object())) + , m_workbook(workbook) +{ +} + +WorkbookObject::~WorkbookObject() +{ +} + +void WorkbookObject::initialize(JS::GlobalObject& global_object) +{ + Object::initialize(global_object); + define_native_function("sheet", sheet, 1); +} + +JS_DEFINE_NATIVE_FUNCTION(WorkbookObject::sheet) +{ + if (interpreter.argument_count() != 1) { + interpreter.throw_exception<JS::TypeError>("Expected exactly one argument to sheet()"); + return {}; + } + auto name_value = interpreter.argument(0); + if (!name_value.is_string() && !name_value.is_number()) { + interpreter.throw_exception<JS::TypeError>("Expected a String or Number argument to sheet()"); + return {}; + } + + auto* this_object = interpreter.this_value(global_object).to_object(interpreter, global_object); + if (!this_object) + return {}; + + if (!this_object->inherits("WorkbookObject")) { + interpreter.throw_exception<JS::TypeError>(JS::ErrorType::NotA, "WorkbookObject"); + return {}; + } + + auto& workbook = static_cast<WorkbookObject*>(this_object)->m_workbook; + + if (name_value.is_string()) { + auto& name = name_value.as_string().string(); + for (auto& sheet : workbook.sheets()) { + if (sheet.name() == name) + return JS::Value(&sheet.global_object()); + } + } else { + auto index = name_value.as_size_t(); + if (index < workbook.sheets().size()) + return JS::Value(&workbook.sheets()[index].global_object()); + } + + return JS::js_undefined(); +} + +} diff --git a/Applications/Spreadsheet/JSIntegration.h b/Applications/Spreadsheet/JSIntegration.h new file mode 100644 index 0000000000..bd3bd1e77f --- /dev/null +++ b/Applications/Spreadsheet/JSIntegration.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LibJS/Forward.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace Spreadsheet { + +class Sheet; +class Workbook; + +class SheetGlobalObject : public JS::GlobalObject { + JS_OBJECT(SheetGlobalObject, JS::GlobalObject); + +public: + SheetGlobalObject(Sheet& sheet); + + virtual ~SheetGlobalObject() override; + + virtual JS::Value get(const JS::PropertyName& name, JS::Value receiver = {}) const override; + virtual bool put(const JS::PropertyName& name, JS::Value value, JS::Value receiver = {}) override; + virtual void initialize() override; + + JS_DECLARE_NATIVE_FUNCTION(parse_cell_name); + +private: + Sheet& m_sheet; +}; + +class WorkbookObject : public JS::Object { + JS_OBJECT(WorkbookObject, JS::Object); + +public: + WorkbookObject(Workbook& workbook); + + virtual ~WorkbookObject() override; + + virtual void initialize(JS::GlobalObject&) override; + + JS_DECLARE_NATIVE_FUNCTION(sheet); + +private: + Workbook& m_workbook; +}; + +} diff --git a/Applications/Spreadsheet/Spreadsheet.cpp b/Applications/Spreadsheet/Spreadsheet.cpp index e90bfc9610..de8410d06d 100644 --- a/Applications/Spreadsheet/Spreadsheet.cpp +++ b/Applications/Spreadsheet/Spreadsheet.cpp @@ -25,96 +25,20 @@ */ #include "Spreadsheet.h" +#include "JSIntegration.h" +#include "Workbook.h" #include <AK/GenericLexer.h> #include <AK/JsonArray.h> #include <AK/JsonObject.h> #include <AK/JsonParser.h> #include <LibCore/File.h> #include <LibJS/Parser.h> -#include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/Function.h> -#include <LibJS/Runtime/GlobalObject.h> -#include <LibJS/Runtime/Object.h> -#include <LibJS/Runtime/Value.h> namespace Spreadsheet { -class SheetGlobalObject : public JS::GlobalObject { - JS_OBJECT(SheetGlobalObject, JS::GlobalObject); - -public: - SheetGlobalObject(Sheet& sheet) - : m_sheet(sheet) - { - } - - virtual ~SheetGlobalObject() override - { - } - - virtual JS::Value get(const JS::PropertyName& name, JS::Value receiver = {}) const override - { - if (name.is_string()) { - if (auto pos = Sheet::parse_cell_name(name.as_string()); pos.has_value()) { - auto& cell = m_sheet.ensure(pos.value()); - cell.reference_from(m_sheet.current_evaluated_cell()); - return cell.js_data(); - } - } - - return GlobalObject::get(name, receiver); - } - - virtual bool put(const JS::PropertyName& name, JS::Value value, JS::Value receiver = {}) override - { - if (name.is_string()) { - if (auto pos = Sheet::parse_cell_name(name.as_string()); pos.has_value()) { - auto& cell = m_sheet.ensure(pos.value()); - if (auto current = m_sheet.current_evaluated_cell()) - current->reference_from(&cell); - - cell.set_data(value); // FIXME: This produces un-savable state! - return true; - } - } - - return GlobalObject::put(name, value, receiver); - } - - virtual void initialize() override - { - GlobalObject::initialize(); - define_native_function("parse_cell_name", parse_cell_name, 1); - } - - static JS_DEFINE_NATIVE_FUNCTION(parse_cell_name) - { - if (interpreter.argument_count() != 1) { - interpreter.throw_exception<JS::TypeError>("Expected exactly one argument to parse_cell_name()"); - return {}; - } - auto name_value = interpreter.argument(0); - if (!name_value.is_string()) { - interpreter.throw_exception<JS::TypeError>("Expected a String argument to parse_cell_name()"); - return {}; - } - auto position = Sheet::parse_cell_name(name_value.as_string().string()); - if (!position.has_value()) - return JS::js_undefined(); - - auto object = JS::Object::create_empty(interpreter.global_object()); - object->put("column", JS::js_string(interpreter, position.value().column)); - object->put("row", JS::Value((unsigned)position.value().row)); - - return object; - } - -private: - Sheet& m_sheet; -}; - -Sheet::Sheet(const StringView& name) - : Sheet(EmptyConstruct::EmptyConstructTag) +Sheet::Sheet(const StringView& name, Workbook& workbook) + : Sheet(workbook) { m_name = name; @@ -125,14 +49,40 @@ Sheet::Sheet(const StringView& name) add_column(); } -Sheet::Sheet(EmptyConstruct) - : m_interpreter(JS::Interpreter::create<SheetGlobalObject>(*this)) +Sheet::Sheet(Workbook& workbook) + : m_workbook(workbook) { + 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->put("thisSheet", m_global_object); // Self-reference is unfortunate, but required. + + // Sadly, these have to be evaluated once per sheet. auto file_or_error = Core::File::open("/res/js/Spreadsheet/runtime.js", Core::IODevice::OpenMode::ReadOnly); if (!file_or_error.is_error()) { auto buffer = file_or_error.value()->read_all(); - evaluate(buffer); + JS::Parser parser { JS::Lexer(buffer) }; + if (parser.has_errors()) { + dbg() << "Spreadsheet: Failed to parse runtime code"; + for (auto& error : parser.errors()) + dbg() << "Error: " << error.to_string() << "\n" + << error.source_location_hint(buffer); + } else { + interpreter().run(global_object(), parser.parse_program()); + if (auto exc = interpreter().exception()) { + dbg() << "Spreadsheet: Failed to run runtime code: "; + for (auto& t : exc->trace()) + dbg() << t; + interpreter().clear_exception(); + } + } } + +} + +JS::Interpreter& Sheet::interpreter() const +{ + return m_workbook.interpreter(); } Sheet::~Sheet() @@ -208,14 +158,14 @@ JS::Value Sheet::evaluate(const StringView& source, Cell* on_behalf_of) return JS::js_undefined(); auto program = parser.parse_program(); - m_interpreter->run(m_interpreter->global_object(), program); - if (m_interpreter->exception()) { - auto exc = m_interpreter->exception()->value(); - m_interpreter->clear_exception(); + interpreter().run(global_object(), program); + if (interpreter().exception()) { + auto exc = interpreter().exception()->value(); + interpreter().clear_exception(); return exc; } - auto value = m_interpreter->last_value(); + auto value = interpreter().last_value(); if (value.is_empty()) return JS::js_undefined(); return value; @@ -309,9 +259,9 @@ void Cell::reference_from(Cell* other) referencing_cells.append(other->make_weak_ptr()); } -RefPtr<Sheet> Sheet::from_json(const JsonObject& object) +RefPtr<Sheet> Sheet::from_json(const JsonObject& object, Workbook& workbook) { - auto sheet = adopt(*new Sheet(EmptyConstruct::EmptyConstructTag)); + auto sheet = adopt(*new Sheet(workbook)); auto rows = object.get("rows").to_u32(20); auto columns = object.get("columns"); if (!columns.is_array()) @@ -385,8 +335,8 @@ JsonObject Sheet::to_json() const data.set("kind", it.value->kind == Cell::Kind::Formula ? "Formula" : "LiteralString"); if (it.value->kind == Cell::Formula) { data.set("source", it.value->data); - auto json = m_interpreter->global_object().get("JSON"); - auto stringified = m_interpreter->call(json.as_object().get("stringify").as_function(), json, it.value->evaluated_data); + auto json = interpreter().global_object().get("JSON"); + auto stringified = interpreter().call(json.as_object().get("stringify").as_function(), json, it.value->evaluated_data); data.set("value", stringified.to_string_without_side_effects()); } else { data.set("value", it.value->data); @@ -404,19 +354,19 @@ JsonObject Sheet::gather_documentation() const JsonObject object; const JS::PropertyName doc_name { "__documentation" }; - auto& global_object = m_interpreter->global_object(); - for (auto& it : global_object.shape().property_table()) { + auto add_docs_from = [&](auto& it, auto& global_object) { auto value = global_object.get(it.key); - if (!value.is_function()) - continue; + if (!value.is_function() && !value.is_object()) + return; - auto& fn = value.as_function(); - if (!fn.has_own_property(doc_name)) - continue; + auto& value_object = value.is_object() ? value.as_object() : value.as_function(); + if (!value_object.has_own_property(doc_name)) + return; - auto doc = fn.get(doc_name); + dbg() << "Found '" << it.key.to_display_string() << "'"; + auto doc = value_object.get(doc_name); if (!doc.is_string()) - continue; + return; JsonParser parser(doc.to_string_without_side_effects()); auto doc_object = parser.parse(); @@ -425,7 +375,13 @@ JsonObject Sheet::gather_documentation() const object.set(it.key.to_display_string(), doc_object.value()); else dbg() << "Sheet::gather_documentation(): Failed to parse the documentation for '" << it.key.to_display_string() << "'!"; - } + }; + + for (auto& it : interpreter().global_object().shape().property_table()) + add_docs_from(it, interpreter().global_object()); + + for (auto& it : global_object().shape().property_table()) + add_docs_from(it, global_object()); return object; } diff --git a/Applications/Spreadsheet/Spreadsheet.h b/Applications/Spreadsheet/Spreadsheet.h index 46c5ab040e..505525922c 100644 --- a/Applications/Spreadsheet/Spreadsheet.h +++ b/Applications/Spreadsheet/Spreadsheet.h @@ -39,6 +39,9 @@ namespace Spreadsheet { +class Workbook; +class SheetGlobalObject; + struct Position { String column; size_t row { 0 }; @@ -140,7 +143,7 @@ public: static Optional<Position> parse_cell_name(const StringView&); JsonObject to_json() const; - static RefPtr<Sheet> from_json(const JsonObject&); + static RefPtr<Sheet> from_json(const JsonObject&, Workbook&); const String& name() const { return m_name; } void set_name(const StringView& name) { m_name = name; } @@ -183,16 +186,15 @@ public: void update(Cell&); JS::Value evaluate(const StringView&, Cell* = nullptr); - JS::Interpreter& interpreter() { return *m_interpreter; } + JS::Interpreter& interpreter() const; + SheetGlobalObject& global_object() const { return *m_global_object; } Cell*& current_evaluated_cell() { return m_current_cell_being_evaluated; } bool has_been_visited(Cell* cell) const { return m_visited_cells_in_update.contains(cell); } private: - enum class EmptyConstruct { EmptyConstructTag }; - - explicit Sheet(EmptyConstruct); - explicit Sheet(const StringView& name); + explicit Sheet(Workbook&); + explicit Sheet(const StringView& name, Workbook&); String m_name; Vector<String> m_columns; @@ -200,11 +202,13 @@ private: HashMap<Position, NonnullOwnPtr<Cell>> m_cells; Optional<Position> m_selected_cell; // FIXME: Make this a collection. + Workbook& m_workbook; + mutable SheetGlobalObject* m_global_object; + Cell* m_current_cell_being_evaluated { nullptr }; size_t m_current_column_name_length { 0 }; - mutable NonnullOwnPtr<JS::Interpreter> m_interpreter; HashTable<Cell*> m_visited_cells_in_update; }; diff --git a/Applications/Spreadsheet/SpreadsheetWidget.cpp b/Applications/Spreadsheet/SpreadsheetWidget.cpp index b56c93857a..7cf6149ee1 100644 --- a/Applications/Spreadsheet/SpreadsheetWidget.cpp +++ b/Applications/Spreadsheet/SpreadsheetWidget.cpp @@ -81,13 +81,13 @@ SpreadsheetWidget::SpreadsheetWidget(NonnullRefPtrVector<Sheet>&& sheets, bool s if (!m_workbook->has_sheets() && should_add_sheet_if_empty) m_workbook->add_sheet("Sheet 1"); - setup_tabs(); + setup_tabs(m_workbook->sheets()); } -void SpreadsheetWidget::setup_tabs() +void SpreadsheetWidget::setup_tabs(NonnullRefPtrVector<Sheet> new_sheets) { RefPtr<GUI::Widget> first_tab_widget; - for (auto& sheet : m_workbook->sheets()) { + for (auto& sheet : new_sheets) { auto& tab = m_tab_widget->add_tab<SpreadsheetView>(sheet.name(), sheet); if (!first_tab_widget) first_tab_widget = &tab; @@ -148,7 +148,20 @@ void SpreadsheetWidget::load(const StringView& filename) m_tab_widget->remove_tab(*widget); } - setup_tabs(); + setup_tabs(m_workbook->sheets()); +} + +void SpreadsheetWidget::add_sheet() +{ + StringBuilder name; + name.append("Sheet"); + name.appendf(" %d", m_workbook->sheets().size() + 1); + + auto& sheet = m_workbook->add_sheet(name.string_view()); + + NonnullRefPtrVector<Sheet> new_sheets; + new_sheets.append(sheet); + setup_tabs(new_sheets); } void SpreadsheetWidget::set_filename(const String& filename) diff --git a/Applications/Spreadsheet/SpreadsheetWidget.h b/Applications/Spreadsheet/SpreadsheetWidget.h index c32483f455..a474a5829b 100644 --- a/Applications/Spreadsheet/SpreadsheetWidget.h +++ b/Applications/Spreadsheet/SpreadsheetWidget.h @@ -41,6 +41,7 @@ public: void save(const StringView& filename); void load(const StringView& filename); + void add_sheet(); const String& current_filename() const { return m_workbook->current_filename(); } void set_filename(const String& filename); @@ -48,7 +49,7 @@ public: private: explicit SpreadsheetWidget(NonnullRefPtrVector<Sheet>&& sheets = {}, bool should_add_sheet_if_empty = true); - void setup_tabs(); + void setup_tabs(NonnullRefPtrVector<Sheet> new_sheets); SpreadsheetView* m_selected_view { nullptr }; RefPtr<GUI::Label> m_current_cell_label; diff --git a/Applications/Spreadsheet/Workbook.cpp b/Applications/Spreadsheet/Workbook.cpp index 8f20eb9897..27e839e4ea 100644 --- a/Applications/Spreadsheet/Workbook.cpp +++ b/Applications/Spreadsheet/Workbook.cpp @@ -25,15 +25,26 @@ */ #include "Workbook.h" +#include "JSIntegration.h" #include <AK/JsonArray.h> #include <AK/JsonObject.h> #include <AK/JsonObjectSerializer.h> #include <AK/JsonParser.h> #include <LibCore/File.h> +#include <LibJS/Parser.h> +#include <LibJS/Runtime/GlobalObject.h> #include <string.h> namespace Spreadsheet { +Workbook::Workbook(NonnullRefPtrVector<Sheet>&& sheets) + : m_sheets(move(sheets)) + , m_interpreter(JS::Interpreter::create<JS::GlobalObject>()) +{ + m_workbook_object = interpreter().heap().allocate<WorkbookObject>(global_object(), *this); + global_object().put("workbook", workbook_object()); +} + bool Workbook::set_filename(const String& filename) { if (m_current_filename == filename) @@ -81,7 +92,7 @@ Result<bool, String> Workbook::load(const StringView& filename) if (!sheet_json.is_object()) return IterationDecision::Continue; - auto sheet = Sheet::from_json(sheet_json.as_object()); + auto sheet = Sheet::from_json(sheet_json.as_object(), *this); if (!sheet) return IterationDecision::Continue; diff --git a/Applications/Spreadsheet/Workbook.h b/Applications/Spreadsheet/Workbook.h index e3ec253ad3..54295fbe67 100644 --- a/Applications/Spreadsheet/Workbook.h +++ b/Applications/Spreadsheet/Workbook.h @@ -32,12 +32,11 @@ namespace Spreadsheet { +class WorkbookObject; + class Workbook { public: - Workbook(NonnullRefPtrVector<Sheet>&& sheets) - : m_sheets(move(sheets)) - { - } + Workbook(NonnullRefPtrVector<Sheet>&& sheets); Result<bool, String> save(const StringView& filename); Result<bool, String> load(const StringView& filename); @@ -52,13 +51,23 @@ public: Sheet& add_sheet(const StringView& name) { - auto sheet = Sheet::construct(name); + auto sheet = Sheet::construct(name, *this); m_sheets.append(sheet); return *sheet; } + JS::Interpreter& interpreter() { return *m_interpreter; } + const JS::Interpreter& interpreter() const { return *m_interpreter; } + + JS::GlobalObject& global_object() { return m_interpreter->global_object(); } + const JS::GlobalObject& global_object() const { return m_interpreter->global_object(); } + + WorkbookObject* workbook_object() { return m_workbook_object; } + private: NonnullRefPtrVector<Sheet> m_sheets; + NonnullOwnPtr<JS::Interpreter> m_interpreter; + WorkbookObject* m_workbook_object { nullptr }; String m_current_filename; }; diff --git a/Applications/Spreadsheet/main.cpp b/Applications/Spreadsheet/main.cpp index d45f4c0ba6..6e0f6e9ef9 100644 --- a/Applications/Spreadsheet/main.cpp +++ b/Applications/Spreadsheet/main.cpp @@ -95,6 +95,9 @@ int main(int argc, char* argv[]) auto menubar = GUI::MenuBar::construct(); auto& app_menu = menubar->add_menu("Spreadsheet"); + app_menu.add_action(GUI::Action::create("Add New Sheet", Gfx::Bitmap::load_from_file("/res/icons/16x16/new-tab.png"), [&](auto&) { + spreadsheet_widget.add_sheet(); + })); app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(0); })); |