Skip to content

Commit f159d16

Browse files
alimpfardawesomekling
authored andcommitted
Spreadsheet: Let the cells know their own position in the sheet
1 parent 13ce24d commit f159d16

File tree

7 files changed

+227
-22
lines changed

7 files changed

+227
-22
lines changed

Applications/Spreadsheet/Cell.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,30 @@
3030
#include "ConditionalFormatting.h"
3131
#include "Forward.h"
3232
#include "JSIntegration.h"
33+
#include "Position.h"
3334
#include <AK/String.h>
3435
#include <AK/Types.h>
3536
#include <AK/WeakPtr.h>
3637

3738
namespace Spreadsheet {
3839

3940
struct Cell : public Weakable<Cell> {
40-
Cell(String data, WeakPtr<Sheet> sheet)
41+
Cell(String data, Position position, WeakPtr<Sheet> sheet)
4142
: dirty(false)
4243
, data(move(data))
4344
, kind(LiteralString)
4445
, sheet(sheet)
46+
, m_position(move(position))
4547
{
4648
}
4749

48-
Cell(String source, JS::Value&& cell_value, WeakPtr<Sheet> sheet)
50+
Cell(String source, JS::Value&& cell_value, Position position, WeakPtr<Sheet> sheet)
4951
: dirty(false)
5052
, data(move(source))
5153
, evaluated_data(move(cell_value))
5254
, kind(Formula)
5355
, sheet(sheet)
56+
, m_position(move(position))
5457
{
5558
}
5659

@@ -63,6 +66,13 @@ struct Cell : public Weakable<Cell> {
6366
void set_type(const CellType*);
6467
void set_type_metadata(CellTypeMetadata&&);
6568

69+
const Position& position() const { return m_position; }
70+
void set_position(Position position, Badge<Sheet>)
71+
{
72+
dirty = true;
73+
m_position = move(position);
74+
}
75+
6676
const Format& evaluated_formats() const { return m_evaluated_formats; }
6777
const Vector<ConditionalFormat>& conditional_formats() const { return m_conditional_formats; }
6878
void set_conditional_formats(Vector<ConditionalFormat>&& fmts)
@@ -99,6 +109,7 @@ struct Cell : public Weakable<Cell> {
99109
Vector<WeakPtr<Cell>> referencing_cells;
100110
const CellType* m_type { nullptr };
101111
CellTypeMetadata m_type_metadata;
112+
Position m_position;
102113

103114
Vector<ConditionalFormat> m_conditional_formats;
104115
Format m_evaluated_formats;

Applications/Spreadsheet/JSIntegration.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ void SheetGlobalObject::initialize()
8282
{
8383
GlobalObject::initialize();
8484
define_native_function("parse_cell_name", parse_cell_name, 1);
85+
define_native_function("current_cell_position", current_cell_position, 0);
8586
}
8687

8788
JS_DEFINE_NATIVE_FUNCTION(SheetGlobalObject::parse_cell_name)
@@ -106,6 +107,36 @@ JS_DEFINE_NATIVE_FUNCTION(SheetGlobalObject::parse_cell_name)
106107
return object;
107108
}
108109

110+
JS_DEFINE_NATIVE_FUNCTION(SheetGlobalObject::current_cell_position)
111+
{
112+
if (vm.argument_count() != 0) {
113+
vm.throw_exception<JS::TypeError>(global_object, "Expected no arguments to current_cell_position()");
114+
return {};
115+
}
116+
117+
auto* this_object = vm.this_value(global_object).to_object(global_object);
118+
if (!this_object)
119+
return JS::js_null();
120+
121+
if (StringView("SheetGlobalObject") != this_object->class_name()) {
122+
vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "SheetGlobalObject");
123+
return {};
124+
}
125+
126+
auto sheet_object = static_cast<SheetGlobalObject*>(this_object);
127+
auto* current_cell = sheet_object->m_sheet.current_evaluated_cell();
128+
if (!current_cell)
129+
return JS::js_null();
130+
131+
auto position = current_cell->position();
132+
133+
auto object = JS::Object::create_empty(global_object);
134+
object->put("column", JS::js_string(vm, position.column));
135+
object->put("row", JS::Value((unsigned)position.row));
136+
137+
return object;
138+
}
139+
109140
WorkbookObject::WorkbookObject(Workbook& workbook)
110141
: JS::Object(*JS::Object::create_empty(workbook.global_object()))
111142
, m_workbook(workbook)

Applications/Spreadsheet/JSIntegration.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class SheetGlobalObject final : public JS::GlobalObject {
4545
virtual void initialize() override;
4646

4747
JS_DECLARE_NATIVE_FUNCTION(parse_cell_name);
48+
JS_DECLARE_NATIVE_FUNCTION(current_cell_position);
4849

4950
private:
5051
Sheet& m_sheet;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2020, the SerenityOS developers.
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are met:
7+
*
8+
* 1. Redistributions of source code must retain the above copyright notice, this
9+
* list of conditions and the following disclaimer.
10+
*
11+
* 2. Redistributions in binary form must reproduce the above copyright notice,
12+
* this list of conditions and the following disclaimer in the documentation
13+
* and/or other materials provided with the distribution.
14+
*
15+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
*/
26+
27+
#pragma once
28+
29+
#include <AK/String.h>
30+
#include <AK/Types.h>
31+
32+
namespace Spreadsheet {
33+
34+
struct Position {
35+
String column;
36+
size_t row { 0 };
37+
38+
bool operator==(const Position& other) const
39+
{
40+
return row == other.row && column == other.column;
41+
}
42+
43+
bool operator!=(const Position& other) const
44+
{
45+
return !(other == *this);
46+
}
47+
};
48+
49+
}

Applications/Spreadsheet/Spreadsheet.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,12 @@ RefPtr<Sheet> Sheet::from_json(const JsonObject& object, Workbook& workbook)
245245
OwnPtr<Cell> cell;
246246
switch (kind) {
247247
case Cell::LiteralString:
248-
cell = make<Cell>(obj.get("value").to_string(), sheet->make_weak_ptr());
248+
cell = make<Cell>(obj.get("value").to_string(), position, sheet->make_weak_ptr());
249249
break;
250250
case Cell::Formula: {
251251
auto& interpreter = sheet->interpreter();
252252
auto value = interpreter.call(parse_function, json, JS::js_string(interpreter.heap(), obj.get("value").as_string()));
253-
cell = make<Cell>(obj.get("source").to_string(), move(value), sheet->make_weak_ptr());
253+
cell = make<Cell>(obj.get("source").to_string(), move(value), position, sheet->make_weak_ptr());
254254
break;
255255
}
256256
}

Applications/Spreadsheet/Spreadsheet.h

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,6 @@
4141

4242
namespace Spreadsheet {
4343

44-
struct Position {
45-
String column;
46-
size_t row { 0 };
47-
48-
bool operator==(const Position& other) const
49-
{
50-
return row == other.row && column == other.column;
51-
}
52-
53-
bool operator!=(const Position& other) const
54-
{
55-
return !(other == *this);
56-
}
57-
};
58-
5944
class Sheet : public Core::Object {
6045
C_OBJECT(Sheet);
6146

@@ -89,7 +74,7 @@ class Sheet : public Core::Object {
8974
if (auto cell = at(position))
9075
return *cell;
9176

92-
m_cells.set(position, make<Cell>(String::empty(), make_weak_ptr()));
77+
m_cells.set(position, make<Cell>(String::empty(), position, make_weak_ptr()));
9378
return *at(position);
9479
}
9580

Base/res/js/Spreadsheet/runtime.js

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,82 @@
1+
// FIXME: Figure out a way to document non-function entities too.
2+
class Position {
3+
constructor(column, row, sheet) {
4+
this.column = column;
5+
this.row = row;
6+
this.sheet = sheet ?? thisSheet;
7+
this.name = `${column}${row}`;
8+
}
9+
10+
static from_name(name) {
11+
let sheet = thisSheet;
12+
let obj = sheet.parse_cell_name(name);
13+
return new Position(obj.column, obj.row, sheet);
14+
}
15+
16+
up(how_many) {
17+
how_many = how_many ?? 1;
18+
const row = Math.max(0, this.row - how_many);
19+
return new Position(this.column, row, this.sheet);
20+
}
21+
22+
down(how_many) {
23+
how_many = how_many ?? 1;
24+
const row = Math.max(0, this.row + how_many);
25+
return new Position(this.column, row, this.sheet);
26+
}
27+
28+
left(how_many) {
29+
how_many = how_many ?? 1;
30+
const column = Math.min(
31+
"Z".charCodeAt(0),
32+
Math.max("A".charCodeAt(0), this.column.charCodeAt(0) - how_many)
33+
);
34+
return new Position(String.fromCharCode(column), this.row, this.sheet);
35+
}
36+
37+
right(how_many) {
38+
how_many = how_many ?? 1;
39+
const column = Math.min(
40+
"Z".charCodeAt(0),
41+
Math.max("A".charCodeAt(0), this.column.charCodeAt(0) + how_many)
42+
);
43+
return new Position(String.fromCharCode(column), this.row, this.sheet);
44+
}
45+
46+
with_column(value) {
47+
return new Position(value, this.row, this.sheet);
48+
}
49+
50+
with_row(value) {
51+
return new Position(this.column, value, this.sheet);
52+
}
53+
54+
in_sheet(the_sheet) {
55+
return new Position(this.column, this.row, sheet(the_sheet));
56+
}
57+
58+
value() {
59+
return this.sheet[this.name];
60+
}
61+
62+
valueOf() {
63+
return value();
64+
}
65+
66+
toString() {
67+
return `<Cell at ${this.name}>`;
68+
}
69+
}
70+
171
function range(start, end, columnStep, rowStep) {
272
columnStep = integer(columnStep ?? 1);
373
rowStep = integer(rowStep ?? 1);
4-
start = parse_cell_name(start) ?? { column: "A", row: 0 };
5-
end = parse_cell_name(end) ?? start;
74+
if (!(start instanceof Position)) {
75+
start = parse_cell_name(start) ?? { column: "A", row: 0 };
76+
}
77+
if (!(end instanceof Position)) {
78+
end = parse_cell_name(end) ?? start;
79+
}
680

781
if (end.column.length > 1 || start.column.length > 1)
882
throw new TypeError("Only single-letter column names are allowed (TODO)");
@@ -172,6 +246,21 @@ function stddev(cells) {
172246
return Math.sqrt(variance(cells));
173247
}
174248

249+
// Lookup
250+
251+
function row() {
252+
return thisSheet.current_cell_position().row;
253+
}
254+
255+
function column() {
256+
return thisSheet.current_cell_position().column;
257+
}
258+
259+
function here() {
260+
const position = current_cell_position();
261+
return new Position(position.column, position.row, thisSheet);
262+
}
263+
175264
// Cheat the system and add documentation
176265
range.__documentation = JSON.stringify({
177266
name: "range",
@@ -382,3 +471,42 @@ stddev.__documentation = JSON.stringify({
382471
'stddev(range("A0", "C4"))': "Calculate the standard deviation of the values in A0:C4",
383472
},
384473
});
474+
475+
row.__documentation = JSON.stringify({
476+
name: "row",
477+
argc: 0,
478+
argnames: [],
479+
doc: "Returns the row number of the current cell",
480+
examples: {},
481+
});
482+
483+
column.__documentation = JSON.stringify({
484+
name: "column",
485+
argc: 0,
486+
argnames: [],
487+
doc: "Returns the column name of the current cell",
488+
examples: {},
489+
});
490+
491+
here.__documentation = JSON.stringify({
492+
name: "here",
493+
argc: 0,
494+
argnames: [],
495+
doc:
496+
"Returns an object representing the current cell's position, see `Position` below.\n\n" +
497+
"## Position\na `Position` is an object representing a given cell position in a given sheet.\n" +
498+
"### Methods:\n- `up(count = 1)`: goes up count cells, or returns the top position if at the top\n" +
499+
"- `down(count = 1)`: goes down count cells\n" +
500+
"- `left(count = 1)`: Goes left count cells, or returns the leftmost position if the edge\n" +
501+
"- `right(count = 1)`: Goes right count cells.\n" +
502+
"- `with_row(row)`: Returns a Position with its column being this object's, and its row being the provided the value.\n" +
503+
"- `with_column(column)`: Similar to `with_row()`, but changes the column instead.\n" +
504+
"- `in_sheet(the_sheet)`: Returns a Position with the same column and row as this one, but with its sheet being `the_sheet`.\n" +
505+
"- `value()`: Returns the value at the position which it represents, in the object's sheet (current sheet by default).\n\n" +
506+
"**NOTE**: Currently only supports single-letter column names",
507+
examples: {
508+
"here().up().value()": "Get the value of the cell above this one",
509+
"here().up().with_column('A')":
510+
"Get a Position above this one in column A, for instance, evaluates to A2 if run in B3.",
511+
},
512+
});

0 commit comments

Comments
 (0)