Spreadsheet: Start making a spreadsheet application
This commit is contained in:
parent
5b5ba91335
commit
a6ebd29aa5
Notes:
sideshowbarker
2024-07-19 03:13:08 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/a6ebd29aa5c Pull-request: https://github.com/SerenityOS/serenity/pull/3279 Reviewed-by: https://github.com/awesomekling Reviewed-by: https://github.com/linusg
13 changed files with 1183 additions and 5 deletions
|
@ -16,6 +16,7 @@ add_subdirectory(Piano)
|
|||
add_subdirectory(PixelPaint)
|
||||
add_subdirectory(QuickShow)
|
||||
add_subdirectory(SoundPlayer)
|
||||
add_subdirectory(Spreadsheet)
|
||||
add_subdirectory(SystemMonitor)
|
||||
add_subdirectory(ThemeEditor)
|
||||
add_subdirectory(Terminal)
|
||||
|
|
10
Applications/Spreadsheet/CMakeLists.txt
Normal file
10
Applications/Spreadsheet/CMakeLists.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
set(SOURCES
|
||||
Spreadsheet.cpp
|
||||
SpreadsheetModel.cpp
|
||||
SpreadsheetView.cpp
|
||||
SpreadsheetWidget.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
serenity_bin(Spreadsheet)
|
||||
target_link_libraries(Spreadsheet LibGUI LibJS)
|
327
Applications/Spreadsheet/Spreadsheet.cpp
Normal file
327
Applications/Spreadsheet/Spreadsheet.cpp
Normal file
|
@ -0,0 +1,327 @@
|
|||
/*
|
||||
* 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 "Spreadsheet.h"
|
||||
#include <AK/GenericLexer.h>
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibJS/Parser.h>
|
||||
#include <LibJS/Runtime/Error.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Object.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)
|
||||
return interpreter.throw_exception<JS::TypeError>("Expected exactly one argument to parse_cell_name()");
|
||||
|
||||
auto name_value = interpreter.argument(0);
|
||||
if (!name_value.is_string())
|
||||
return interpreter.throw_exception<JS::TypeError>("Expected a String argument to parse_cell_name()");
|
||||
|
||||
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)
|
||||
: m_name(name)
|
||||
, m_interpreter(JS::Interpreter::create<SheetGlobalObject>(*this))
|
||||
{
|
||||
for (size_t i = 0; i < 20; ++i)
|
||||
add_row();
|
||||
|
||||
for (size_t i = 0; i < 16; ++i)
|
||||
add_column();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Sheet::~Sheet()
|
||||
{
|
||||
}
|
||||
|
||||
size_t Sheet::add_row()
|
||||
{
|
||||
return m_rows++;
|
||||
}
|
||||
|
||||
String Sheet::add_column()
|
||||
{
|
||||
if (m_current_column_name_length == 0) {
|
||||
m_current_column_name_length = 1;
|
||||
m_columns.append("A");
|
||||
return "A";
|
||||
}
|
||||
|
||||
if (m_current_column_name_length == 1) {
|
||||
auto last_char = m_columns.last()[0];
|
||||
if (last_char == 'Z') {
|
||||
m_current_column_name_length = 2;
|
||||
m_columns.append("AA");
|
||||
return "AA";
|
||||
}
|
||||
|
||||
last_char++;
|
||||
m_columns.append({ &last_char, 1 });
|
||||
return m_columns.last();
|
||||
}
|
||||
|
||||
TODO();
|
||||
}
|
||||
|
||||
void Sheet::update()
|
||||
{
|
||||
m_visited_cells_in_update.clear();
|
||||
for (auto& it : m_cells) {
|
||||
auto& cell = *it.value;
|
||||
if (has_been_visited(&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();
|
||||
}
|
||||
|
||||
void Sheet::update(Cell& cell)
|
||||
{
|
||||
if (has_been_visited(&cell))
|
||||
return;
|
||||
|
||||
m_visited_cells_in_update.set(&cell);
|
||||
cell.update({});
|
||||
}
|
||||
|
||||
JS::Value Sheet::evaluate(const StringView& source, Cell* on_behalf_of)
|
||||
{
|
||||
TemporaryChange cell_change { m_current_cell_being_evaluated, on_behalf_of };
|
||||
|
||||
auto parser = JS::Parser(JS::Lexer(source));
|
||||
if (parser.has_errors())
|
||||
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();
|
||||
return exc;
|
||||
}
|
||||
|
||||
auto value = m_interpreter->last_value();
|
||||
if (value.is_empty())
|
||||
return JS::js_undefined();
|
||||
return value;
|
||||
}
|
||||
|
||||
void Cell::update_data()
|
||||
{
|
||||
TemporaryChange cell_change { sheet->current_evaluated_cell(), this };
|
||||
if (!dirty)
|
||||
return;
|
||||
|
||||
dirty = false;
|
||||
if (kind == Formula) {
|
||||
if (!evaluated_externally)
|
||||
evaluated_data = sheet->evaluate(data, this);
|
||||
}
|
||||
|
||||
for (auto& ref : referencing_cells) {
|
||||
if (ref) {
|
||||
ref->dirty = true;
|
||||
ref->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Cell::update()
|
||||
{
|
||||
sheet->update(*this);
|
||||
}
|
||||
|
||||
JS::Value Cell::js_data()
|
||||
{
|
||||
if (dirty)
|
||||
update();
|
||||
|
||||
if (kind == Formula)
|
||||
return evaluated_data;
|
||||
|
||||
return JS::js_string(sheet->interpreter(), data);
|
||||
}
|
||||
|
||||
Cell* Sheet::at(const StringView& name)
|
||||
{
|
||||
auto pos = parse_cell_name(name);
|
||||
if (pos.has_value())
|
||||
return at(pos.value());
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Cell* Sheet::at(const Position& position)
|
||||
{
|
||||
auto it = m_cells.find(position);
|
||||
|
||||
if (it == m_cells.end())
|
||||
return nullptr;
|
||||
|
||||
return it->value;
|
||||
}
|
||||
|
||||
Optional<Position> Sheet::parse_cell_name(const StringView& name)
|
||||
{
|
||||
GenericLexer lexer(name);
|
||||
auto col = lexer.consume_while([](auto c) { return is_alpha(c); });
|
||||
auto row = lexer.consume_while([](auto c) { return is_alphanum(c) && !is_alpha(c); });
|
||||
|
||||
if (!lexer.is_eof() || row.is_empty() || col.is_empty())
|
||||
return {};
|
||||
|
||||
return Position { col, row.to_uint().value() };
|
||||
}
|
||||
|
||||
String Cell::source() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
if (kind == Formula)
|
||||
builder.append('=');
|
||||
builder.append(data);
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
// FIXME: Find a better way to figure out dependencies
|
||||
void Cell::reference_from(Cell* other)
|
||||
{
|
||||
if (!other || other == this)
|
||||
return;
|
||||
|
||||
if (!referencing_cells.find([other](auto& ptr) { return ptr.ptr() == other; }).is_end())
|
||||
return;
|
||||
|
||||
referencing_cells.append(other->make_weak_ptr());
|
||||
}
|
||||
|
||||
JsonObject Sheet::to_json() const
|
||||
{
|
||||
JsonObject object;
|
||||
object.set("name", m_name);
|
||||
|
||||
auto columns = JsonArray();
|
||||
for (auto& column : m_columns)
|
||||
columns.append(column);
|
||||
object.set("columns", move(columns));
|
||||
|
||||
object.set("rows", m_rows);
|
||||
|
||||
JsonObject cells;
|
||||
for (auto& it : m_cells) {
|
||||
StringBuilder builder;
|
||||
builder.append(it.key.column);
|
||||
builder.appendf("%zu", it.key.row);
|
||||
auto key = builder.to_string();
|
||||
|
||||
JsonObject data;
|
||||
data.set("kind", it.value->kind == Cell::Kind::Formula ? "Formula" : "LiteralString");
|
||||
data.set("value", it.value->data);
|
||||
|
||||
cells.set(key, move(data));
|
||||
}
|
||||
object.set("cells", move(cells));
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
}
|
211
Applications/Spreadsheet/Spreadsheet.h
Normal file
211
Applications/Spreadsheet/Spreadsheet.h
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* 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 <AK/HashMap.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Traits.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <AK/Weakable.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibJS/Interpreter.h>
|
||||
|
||||
namespace Spreadsheet {
|
||||
|
||||
struct Position {
|
||||
String column;
|
||||
size_t row { 0 };
|
||||
|
||||
bool operator==(const Position& other) const
|
||||
{
|
||||
return row == other.row && column == other.column;
|
||||
}
|
||||
|
||||
bool operator!=(const Position& other) const
|
||||
{
|
||||
return !(other == *this);
|
||||
}
|
||||
};
|
||||
|
||||
class Sheet;
|
||||
|
||||
struct Cell : public Weakable<Cell> {
|
||||
Cell(String data, WeakPtr<Sheet> sheet)
|
||||
: dirty(false)
|
||||
, data(move(data))
|
||||
, kind(LiteralString)
|
||||
, sheet(sheet)
|
||||
{
|
||||
}
|
||||
|
||||
bool dirty { false };
|
||||
bool evaluated_externally { false };
|
||||
String data;
|
||||
JS::Value evaluated_data;
|
||||
|
||||
enum Kind {
|
||||
LiteralString,
|
||||
Formula,
|
||||
} kind { LiteralString };
|
||||
|
||||
WeakPtr<Sheet> sheet;
|
||||
Vector<WeakPtr<Cell>> referencing_cells;
|
||||
|
||||
void reference_from(Cell*);
|
||||
|
||||
void set_data(String new_data)
|
||||
{
|
||||
if (data == new_data)
|
||||
return;
|
||||
|
||||
if (new_data.starts_with("=")) {
|
||||
new_data = new_data.substring(1, new_data.length() - 1);
|
||||
kind = Formula;
|
||||
} else {
|
||||
kind = LiteralString;
|
||||
}
|
||||
|
||||
data = move(new_data);
|
||||
dirty = true;
|
||||
evaluated_externally = false;
|
||||
}
|
||||
|
||||
void set_data(JS::Value new_data)
|
||||
{
|
||||
dirty = true;
|
||||
evaluated_externally = true;
|
||||
|
||||
StringBuilder builder;
|
||||
|
||||
builder.append("=");
|
||||
builder.append(new_data.to_string_without_side_effects());
|
||||
data = builder.build();
|
||||
|
||||
evaluated_data = move(new_data);
|
||||
}
|
||||
|
||||
String source() const;
|
||||
|
||||
JS::Value js_data();
|
||||
|
||||
void update(Badge<Sheet>) { update_data(); }
|
||||
void update();
|
||||
|
||||
private:
|
||||
void update_data();
|
||||
};
|
||||
|
||||
class Sheet : public Core::Object {
|
||||
C_OBJECT(Sheet);
|
||||
|
||||
public:
|
||||
~Sheet();
|
||||
|
||||
static Optional<Position> parse_cell_name(const StringView&);
|
||||
|
||||
JsonObject to_json() const;
|
||||
|
||||
const String& name() const { return m_name; }
|
||||
void set_name(const StringView& name) { m_name = name; }
|
||||
|
||||
Optional<Position> selected_cell() const { return m_selected_cell; }
|
||||
const HashMap<Position, NonnullOwnPtr<Cell>>& cells() const { return m_cells; }
|
||||
HashMap<Position, NonnullOwnPtr<Cell>>& cells() { return m_cells; }
|
||||
|
||||
Cell* at(const Position& position);
|
||||
const Cell* at(const Position& position) const { return const_cast<Sheet*>(this)->at(position); }
|
||||
|
||||
const Cell* at(const StringView& name) const { return const_cast<Sheet*>(this)->at(name); }
|
||||
Cell* at(const StringView&);
|
||||
|
||||
const Cell& ensure(const Position& position) const { return const_cast<Sheet*>(this)->ensure(position); }
|
||||
Cell& ensure(const Position& position)
|
||||
{
|
||||
if (auto cell = at(position))
|
||||
return *cell;
|
||||
|
||||
m_cells.set(position, make<Cell>(String::empty(), make_weak_ptr()));
|
||||
return *at(position);
|
||||
}
|
||||
|
||||
size_t add_row();
|
||||
String add_column();
|
||||
|
||||
size_t row_count() const { return m_rows; }
|
||||
size_t column_count() const { return m_columns.size(); }
|
||||
const Vector<String>& columns() const { return m_columns; }
|
||||
const String& column(size_t index) const
|
||||
{
|
||||
ASSERT(column_count() > index);
|
||||
return m_columns[index];
|
||||
}
|
||||
|
||||
void update();
|
||||
void update(Cell&);
|
||||
|
||||
JS::Value evaluate(const StringView&, Cell* = nullptr);
|
||||
JS::Interpreter& interpreter() { return *m_interpreter; }
|
||||
|
||||
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:
|
||||
Sheet(const StringView& name);
|
||||
|
||||
String m_name;
|
||||
Vector<String> m_columns;
|
||||
size_t m_rows { 0 };
|
||||
HashMap<Position, NonnullOwnPtr<Cell>> m_cells;
|
||||
Optional<Position> m_selected_cell; // FIXME: Make this a collection.
|
||||
|
||||
Cell* m_current_cell_being_evaluated { nullptr };
|
||||
|
||||
size_t m_current_column_name_length { 0 };
|
||||
|
||||
NonnullOwnPtr<JS::Interpreter> m_interpreter;
|
||||
HashTable<Cell*> m_visited_cells_in_update;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
|
||||
template<>
|
||||
struct Traits<Spreadsheet::Position> : public GenericTraits<Spreadsheet::Position> {
|
||||
static constexpr bool is_trivial() { return false; }
|
||||
static unsigned hash(const Spreadsheet::Position& p)
|
||||
{
|
||||
return pair_int_hash(
|
||||
string_hash(p.column.characters(), p.column.length()),
|
||||
u64_hash(p.row));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
104
Applications/Spreadsheet/SpreadsheetModel.cpp
Normal file
104
Applications/Spreadsheet/SpreadsheetModel.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 "SpreadsheetModel.h"
|
||||
|
||||
namespace Spreadsheet {
|
||||
|
||||
SheetModel::~SheetModel()
|
||||
{
|
||||
}
|
||||
|
||||
GUI::Variant SheetModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
|
||||
{
|
||||
if (!index.is_valid())
|
||||
return {};
|
||||
|
||||
if (role == GUI::ModelRole::Display) {
|
||||
if (index.column() == 0)
|
||||
return String::number(index.row());
|
||||
|
||||
const auto* value = m_sheet->at({ m_sheet->column(index.column() - 1), (size_t)index.row() });
|
||||
if (!value)
|
||||
return String::empty();
|
||||
|
||||
if (value->kind == Spreadsheet::Cell::Formula)
|
||||
return value->evaluated_data.is_empty() ? "" : value->evaluated_data.to_string_without_side_effects();
|
||||
|
||||
return value->data;
|
||||
}
|
||||
|
||||
if (role == GUI::ModelRole::TextAlignment) {
|
||||
if (index.column() == 0)
|
||||
return {};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
String SheetModel::column_name(int index) const
|
||||
{
|
||||
if (index < 0)
|
||||
return {};
|
||||
|
||||
if (index == 0)
|
||||
return "";
|
||||
|
||||
return m_sheet->column(index - 1);
|
||||
}
|
||||
|
||||
bool SheetModel::is_editable(const GUI::ModelIndex& index) const
|
||||
{
|
||||
if (!index.is_valid())
|
||||
return false;
|
||||
|
||||
if (index.column() == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SheetModel::set_data(const GUI::ModelIndex& index, const GUI::Variant& value)
|
||||
{
|
||||
if (!index.is_valid())
|
||||
return;
|
||||
|
||||
if (index.column() == 0)
|
||||
return;
|
||||
|
||||
auto& cell = m_sheet->ensure({ m_sheet->column(index.column() - 1), (size_t)index.row() });
|
||||
cell.set_data(value.to_string());
|
||||
update();
|
||||
}
|
||||
|
||||
void SheetModel::update()
|
||||
{
|
||||
m_sheet->update();
|
||||
}
|
||||
|
||||
}
|
56
Applications/Spreadsheet/SpreadsheetModel.h
Normal file
56
Applications/Spreadsheet/SpreadsheetModel.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 "Spreadsheet.h"
|
||||
#include <LibGUI/Model.h>
|
||||
|
||||
namespace Spreadsheet {
|
||||
|
||||
class SheetModel final : public GUI::Model {
|
||||
public:
|
||||
static NonnullRefPtr<SheetModel> create(Sheet& sheet) { return adopt(*new SheetModel(sheet)); }
|
||||
virtual ~SheetModel() override;
|
||||
|
||||
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_sheet->row_count(); }
|
||||
virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_sheet->column_count() + 1; }
|
||||
virtual String column_name(int) const override;
|
||||
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
|
||||
virtual bool is_editable(const GUI::ModelIndex&) const override;
|
||||
virtual void set_data(const GUI::ModelIndex&, const GUI::Variant&) override;
|
||||
virtual void update() override;
|
||||
|
||||
private:
|
||||
explicit SheetModel(Sheet& sheet)
|
||||
: m_sheet(sheet)
|
||||
{
|
||||
}
|
||||
|
||||
NonnullRefPtr<Sheet> m_sheet;
|
||||
};
|
||||
|
||||
}
|
89
Applications/Spreadsheet/SpreadsheetView.cpp
Normal file
89
Applications/Spreadsheet/SpreadsheetView.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 "SpreadsheetView.h"
|
||||
#include "SpreadsheetModel.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/ModelEditingDelegate.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/TableView.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
|
||||
namespace Spreadsheet {
|
||||
|
||||
SpreadsheetView::~SpreadsheetView()
|
||||
{
|
||||
}
|
||||
|
||||
void SpreadsheetView::EditingDelegate::set_value(const GUI::Variant& value)
|
||||
{
|
||||
if (m_has_set_initial_value)
|
||||
return StringModelEditingDelegate::set_value(value);
|
||||
|
||||
m_has_set_initial_value = true;
|
||||
const auto option = m_sheet.at({ m_sheet.column(index().column() - 1), (size_t)index().row() });
|
||||
if (option)
|
||||
return StringModelEditingDelegate::set_value(option->source());
|
||||
|
||||
StringModelEditingDelegate::set_value("");
|
||||
}
|
||||
|
||||
SpreadsheetView::SpreadsheetView(Sheet& sheet)
|
||||
: m_sheet(sheet)
|
||||
{
|
||||
set_layout<GUI::VerticalBoxLayout>().set_margins({ 2, 2, 2, 2 });
|
||||
m_table_view = add<GUI::TableView>();
|
||||
m_table_view->set_model(SheetModel::create(*m_sheet));
|
||||
|
||||
// FIXME: This is dumb.
|
||||
for (size_t i = 0; i < m_sheet->column_count(); ++i) {
|
||||
m_table_view->set_cell_painting_delegate(i + 1, make<TableCellPainter>(*m_table_view));
|
||||
m_table_view->set_column_width(i + 1, 50);
|
||||
m_table_view->set_column_header_alignment(i + 1, Gfx::TextAlignment::Center);
|
||||
}
|
||||
|
||||
m_table_view->set_alternating_row_colors(false);
|
||||
m_table_view->set_highlight_selected_rows(false);
|
||||
m_table_view->set_editable(true);
|
||||
m_table_view->aid_create_editing_delegate = [&](auto&) {
|
||||
return make<EditingDelegate>(*m_sheet);
|
||||
};
|
||||
}
|
||||
|
||||
void SpreadsheetView::TableCellPainter::paint(GUI::Painter& painter, const Gfx::IntRect& rect, const Gfx::Palette& palette, const GUI::ModelIndex& index)
|
||||
{
|
||||
// Draw a border.
|
||||
// Undo the horizontal padding done by the table view...
|
||||
painter.draw_rect(rect.inflated(m_table_view.horizontal_padding() * 2, 0), palette.ruler());
|
||||
if (m_table_view.selection().contains(index))
|
||||
painter.draw_rect(rect.inflated(m_table_view.horizontal_padding() * 2 + 1, 1), palette.ruler_border());
|
||||
|
||||
auto data = index.data();
|
||||
auto text_alignment = index.data(GUI::ModelRole::TextAlignment).to_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
painter.draw_text(rect, data.to_string(), m_table_view.font_for_index(index), text_alignment, palette.color(m_table_view.foreground_role()), Gfx::TextElision::Right);
|
||||
}
|
||||
|
||||
}
|
81
Applications/Spreadsheet/SpreadsheetView.h
Normal file
81
Applications/Spreadsheet/SpreadsheetView.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 "Spreadsheet.h"
|
||||
#include <LibGUI/AbstractTableView.h>
|
||||
#include <LibGUI/ModelEditingDelegate.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace Spreadsheet {
|
||||
|
||||
class SpreadsheetView final : public GUI::Widget {
|
||||
C_OBJECT(SpreadsheetView);
|
||||
|
||||
public:
|
||||
~SpreadsheetView();
|
||||
|
||||
const Sheet& sheet() const { return *m_sheet; }
|
||||
|
||||
private:
|
||||
SpreadsheetView(Sheet&);
|
||||
|
||||
class EditingDelegate : public GUI::StringModelEditingDelegate {
|
||||
public:
|
||||
EditingDelegate(const Sheet& sheet)
|
||||
: m_sheet(sheet)
|
||||
{
|
||||
}
|
||||
virtual void set_value(const GUI::Variant& value) override;
|
||||
|
||||
private:
|
||||
bool m_has_set_initial_value { false };
|
||||
const Sheet& m_sheet;
|
||||
};
|
||||
|
||||
class TableCellPainter final : public GUI::TableCellPaintingDelegate {
|
||||
public:
|
||||
TableCellPainter(const GUI::TableView& view)
|
||||
: m_table_view(view)
|
||||
{
|
||||
}
|
||||
void paint(GUI::Painter&, const Gfx::IntRect&, const Gfx::Palette&, const GUI::ModelIndex&) override;
|
||||
|
||||
private:
|
||||
const GUI::TableView& m_table_view;
|
||||
};
|
||||
|
||||
NonnullRefPtr<Sheet> m_sheet;
|
||||
RefPtr<GUI::TableView> m_table_view;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
AK_BEGIN_TYPE_TRAITS(Spreadsheet::SpreadsheetView)
|
||||
static bool is_type(const Core::Object& object) { return !strcmp(object.class_name(), "SpreadsheetView"); }
|
||||
AK_END_TYPE_TRAITS()
|
89
Applications/Spreadsheet/SpreadsheetWidget.cpp
Normal file
89
Applications/Spreadsheet/SpreadsheetWidget.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 "SpreadsheetWidget.h"
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonObjectSerializer.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/MessageBox.h>
|
||||
#include <LibGUI/TabWidget.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace Spreadsheet {
|
||||
|
||||
SpreadsheetWidget::SpreadsheetWidget()
|
||||
{
|
||||
set_fill_with_background_color(true);
|
||||
set_layout<GUI::VerticalBoxLayout>().set_margins({ 2, 2, 2, 2 });
|
||||
m_tab_widget = add<GUI::TabWidget>();
|
||||
m_tab_widget->set_tab_position(GUI::TabWidget::TabPosition::Bottom);
|
||||
|
||||
m_sheets.append(Sheet::construct("Sheet 1"));
|
||||
m_tab_widget->add_tab<SpreadsheetView>(m_sheets.first().name(), m_sheets.first());
|
||||
}
|
||||
|
||||
SpreadsheetWidget::~SpreadsheetWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void SpreadsheetWidget::save(const StringView& filename)
|
||||
{
|
||||
JsonArray array;
|
||||
m_tab_widget->for_each_child_of_type<SpreadsheetView>([&](auto& view) {
|
||||
array.append(view.sheet().to_json());
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
auto file_content = array.to_string();
|
||||
|
||||
auto file = Core::File::construct(filename);
|
||||
file->open(Core::IODevice::WriteOnly);
|
||||
if (!file->is_open()) {
|
||||
StringBuilder sb;
|
||||
sb.append("Failed to open ");
|
||||
sb.append(filename);
|
||||
sb.append(" for write. Error: ");
|
||||
sb.append(file->error_string());
|
||||
|
||||
GUI::MessageBox::show(window(), sb.to_string(), "Error", GUI::MessageBox::Type::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
bool result = file->write(file_content);
|
||||
if (!result) {
|
||||
int error_number = errno;
|
||||
StringBuilder sb;
|
||||
sb.append("Unable to save file. Error: ");
|
||||
sb.append(strerror(error_number));
|
||||
|
||||
GUI::MessageBox::show(window(), sb.to_string(), "Error", GUI::MessageBox::Type::Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
50
Applications/Spreadsheet/SpreadsheetWidget.h
Normal file
50
Applications/Spreadsheet/SpreadsheetWidget.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 "SpreadsheetView.h"
|
||||
#include <AK/NonnullRefPtrVector.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
namespace Spreadsheet {
|
||||
|
||||
class SpreadsheetWidget final : public GUI::Widget {
|
||||
C_OBJECT(SpreadsheetWidget);
|
||||
|
||||
public:
|
||||
~SpreadsheetWidget();
|
||||
|
||||
void save(const StringView& filename);
|
||||
|
||||
private:
|
||||
SpreadsheetWidget();
|
||||
|
||||
NonnullRefPtrVector<Sheet> m_sheets;
|
||||
RefPtr<GUI::TabWidget> m_tab_widget;
|
||||
};
|
||||
|
||||
}
|
65
Applications/Spreadsheet/main.cpp
Normal file
65
Applications/Spreadsheet/main.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 "Spreadsheet.h"
|
||||
#include "SpreadsheetWidget.h"
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibGUI/Application.h>
|
||||
#include <LibGUI/Forward.h>
|
||||
#include <LibGUI/Window.h>
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
Core::ArgsParser args_parser;
|
||||
args_parser.parse(argc, argv);
|
||||
|
||||
auto app = GUI::Application::construct(argc, argv);
|
||||
|
||||
if (pledge("stdio thread rpath accept cpath wpath shared_buffer unix", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/res", "r") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil(nullptr, nullptr) < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto window = GUI::Window::construct();
|
||||
window->set_title("Spreadsheet");
|
||||
window->resize(640, 480);
|
||||
|
||||
window->set_main_widget<Spreadsheet::SpreadsheetWidget>();
|
||||
|
||||
window->show();
|
||||
|
||||
return app->exec();
|
||||
}
|
93
Base/res/js/Spreadsheet/runtime.js
Normal file
93
Base/res/js/Spreadsheet/runtime.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
const sheet = this
|
||||
|
||||
function range(start, end, column_step, row_step) {
|
||||
column_step = integer(column_step ?? 1)
|
||||
row_step = integer(row_step ?? 1)
|
||||
start = sheet.parse_cell_name(start) ?? {column: 'A', row: 0}
|
||||
end = sheet.parse_cell_name(end) ?? start
|
||||
|
||||
if (end.column.length > 1 || start.column.length > 1)
|
||||
throw new TypeError("Only single-letter column names are allowed (TODO)");
|
||||
|
||||
const cells = []
|
||||
|
||||
for (let col = Math.min(start.column.charCodeAt(0), end.column.charCodeAt(0));
|
||||
col <= Math.max(start.column.charCodeAt(0), end.column.charCodeAt(0));
|
||||
++col) {
|
||||
for (let row = Math.min(start.row, end.row);
|
||||
row <= Math.max(start.row, end.row);
|
||||
++row) {
|
||||
|
||||
cells.push(String.fromCharCode(col) + row)
|
||||
}
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
|
||||
// FIXME: Remove this and use String.split() eventually
|
||||
function split(str, sep) {
|
||||
const parts = []
|
||||
let split_index = -1
|
||||
for(;;) {
|
||||
split_index = str.indexOf(sep)
|
||||
if (split_index == -1) {
|
||||
if (str.length)
|
||||
parts.push(str)
|
||||
return parts
|
||||
}
|
||||
parts.push(str.substring(0, split_index))
|
||||
str = str.slice(split_index + sep.length)
|
||||
}
|
||||
}
|
||||
|
||||
function R(fmt, ...args) {
|
||||
if (args.length !== 0)
|
||||
throw new TypeError("R`` format must be literal")
|
||||
|
||||
fmt = fmt[0]
|
||||
return range(...split(fmt, ':'))
|
||||
}
|
||||
|
||||
function select(criteria, t, f) {
|
||||
if (criteria)
|
||||
return t;
|
||||
return f;
|
||||
}
|
||||
|
||||
function sumif(condition, cells) {
|
||||
let sum = null
|
||||
for (let name of cells) {
|
||||
let cell = sheet[name]
|
||||
if (condition(cell))
|
||||
sum = sum === null ? cell : sum + cell
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
function countif(condition, cells) {
|
||||
let count = 0
|
||||
for (let name of cells) {
|
||||
let cell = sheet[name]
|
||||
if (condition(cell))
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
function now() {
|
||||
return new Date()
|
||||
}
|
||||
|
||||
function repeat(count, str) {
|
||||
return Array(count + 1).join(str)
|
||||
}
|
||||
|
||||
function randrange(min, max) {
|
||||
return Math.random() * (max - min) + min
|
||||
}
|
||||
|
||||
function integer(value) {
|
||||
return value | 0
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ namespace GUI {
|
|||
|
||||
class ModelEditingDelegate {
|
||||
public:
|
||||
virtual ~ModelEditingDelegate() {}
|
||||
virtual ~ModelEditingDelegate() { }
|
||||
|
||||
void bind(Model& model, const ModelIndex& index)
|
||||
{
|
||||
|
@ -53,10 +53,10 @@ public:
|
|||
virtual Variant value() const = 0;
|
||||
virtual void set_value(const Variant&) = 0;
|
||||
|
||||
virtual void will_begin_editing() {}
|
||||
virtual void will_begin_editing() { }
|
||||
|
||||
protected:
|
||||
ModelEditingDelegate() {}
|
||||
ModelEditingDelegate() { }
|
||||
|
||||
virtual RefPtr<Widget> create_widget() = 0;
|
||||
void commit()
|
||||
|
@ -65,6 +65,8 @@ protected:
|
|||
on_commit();
|
||||
}
|
||||
|
||||
const ModelIndex& index() const { return m_index; }
|
||||
|
||||
private:
|
||||
RefPtr<Model> m_model;
|
||||
ModelIndex m_index;
|
||||
|
@ -73,8 +75,8 @@ private:
|
|||
|
||||
class StringModelEditingDelegate : public ModelEditingDelegate {
|
||||
public:
|
||||
StringModelEditingDelegate() {}
|
||||
virtual ~StringModelEditingDelegate() override {}
|
||||
StringModelEditingDelegate() { }
|
||||
virtual ~StringModelEditingDelegate() override { }
|
||||
|
||||
virtual RefPtr<Widget> create_widget() override
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue