diff --git a/.gitignore b/.gitignore
index 1b6985c..e66edb7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,45 @@
-# Ignore Gradle project-specific cache directory
+.vscode
.gradle
+**/build/
+!src/**/build/
-# Ignore Gradle build output directory
-build
+**/bin
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Avoid ignore Gradle wrappper properties
+!gradle-wrapper.properties
+
+# Cache of project
+.gradletasknamecache
+
+# Eclipse Gradle plugin generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+*.minisig
diff --git a/app/src/main/java/expr/Add.java b/app/src/main/java/expr/Add.java
new file mode 100644
index 0000000..ec8e6fb
--- /dev/null
+++ b/app/src/main/java/expr/Add.java
@@ -0,0 +1,18 @@
+package expr;
+
+class Add extends BinaryExpr {
+
+ Add(Expr expr1, Expr expr2) {
+ super(expr1, expr2);
+ precedence1 = 0;
+ precedence2 = 0;
+ }
+
+ public double op(double op1, double op2) {
+ return op1 + op2;
+ }
+
+ protected String opString() {
+ return "+";
+ }
+}
diff --git a/app/src/main/java/expr/BinaryExpr.java b/app/src/main/java/expr/BinaryExpr.java
new file mode 100644
index 0000000..49cb462
--- /dev/null
+++ b/app/src/main/java/expr/BinaryExpr.java
@@ -0,0 +1,37 @@
+package expr;
+
+abstract class BinaryExpr extends Expr {
+
+ private Expr expr1;
+ private Expr expr2;
+ protected int precedence1;
+ protected int precedence2;
+
+ protected BinaryExpr(Expr expr1, Expr expr2) {
+ this.expr1 = expr1;
+ this.expr2 = expr2;
+ }
+
+ protected abstract double op(double op1, double op2);
+
+ protected abstract String opString();
+
+ public String toString(int prec) {
+ StringBuilder builder = new StringBuilder();
+ boolean parentheses = prec > precedence1;
+ if (parentheses) {
+ builder.append('(');
+ }
+ builder.append(expr1.toString(precedence1));
+ builder.append(opString());
+ builder.append(expr2.toString(precedence2));
+ if (parentheses) {
+ builder.append(')');
+ }
+ return builder.toString();
+ }
+
+ public double value(Environment env) {
+ return op(expr1.value(env), expr2.value(env));
+ }
+}
diff --git a/app/src/main/java/expr/Div.java b/app/src/main/java/expr/Div.java
new file mode 100644
index 0000000..b27ef43
--- /dev/null
+++ b/app/src/main/java/expr/Div.java
@@ -0,0 +1,21 @@
+package expr;
+
+import util.XLException;
+
+class Div extends BinaryExpr {
+
+ Div(Expr expr1, Expr expr2) {
+ super(expr1, expr2);
+ precedence1 = 1;
+ precedence2 = 2;
+ }
+
+ public double op(double op1, double op2) {
+ if (op2 != 0) return op1 / op2;
+ else throw new XLException("division by zero");
+ }
+
+ protected String opString() {
+ return "/";
+ }
+}
diff --git a/app/src/main/java/expr/Environment.java b/app/src/main/java/expr/Environment.java
new file mode 100644
index 0000000..eab570c
--- /dev/null
+++ b/app/src/main/java/expr/Environment.java
@@ -0,0 +1,6 @@
+package expr;
+
+public interface Environment {
+
+ public double value(String name);
+}
diff --git a/app/src/main/java/expr/Expr.java b/app/src/main/java/expr/Expr.java
new file mode 100644
index 0000000..81499f5
--- /dev/null
+++ b/app/src/main/java/expr/Expr.java
@@ -0,0 +1,38 @@
+package expr;
+
+/**
+ * An Expr object represents a real valued expression that may contain variables. The
+ * value of a variable is obtained from an Environment object by specifying the name of
+ * the variable.
+ *
+ * @see Environment
+ * @author Lennart Andersson
+ */
+public abstract class Expr {
+
+ /**
+ * The toString method returns a String representation of this
+ * expression without unnecessary parentheses.
+ *
+ * @return the String representation of this expression.
+ */
+ public String toString() {
+ return toString(0);
+ }
+
+ /*
+ * toString(prec) returns a string representation of this expression without
+ * unnecessary parentheses. The prec argument specifies the precedence level
+ * enclosing expression and is used to control the precedence of
+ * parentheses.
+ */
+ public abstract String toString(int prec);
+
+ /**
+ * The value method returns the value of this expression.
+ *
+ * @param env is the Environment containing the values of variables.
+ * @return the double value of this expression.
+ */
+ public abstract double value(Environment env);
+}
diff --git a/app/src/main/java/expr/ExprParser.java b/app/src/main/java/expr/ExprParser.java
new file mode 100644
index 0000000..a933265
--- /dev/null
+++ b/app/src/main/java/expr/ExprParser.java
@@ -0,0 +1,136 @@
+package expr;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.regex.Pattern;
+import util.XLException;
+
+/**
+ * An ExprParser object is a parser provides a factory method for building Expr
+ * objects from text representations of arithmetic expressions. The text containing the
+ * expression should adhere to the following grammar.
+ *
+ *
+ *
+ *
+ *
+ * expr ::= term {addop term}
+ * term ::= factor {mulop factor}
+ * factor ::= number | variable | "(" expr ")"
+ * addop ::= "+" | "-"
+ * mulop ::= "*" | "/"
+ *
+ *
+ *
+ *
+ *
+ * where number is an unsigned number according to StreamTokenizer and
+ * variable is a string of letters and digits. The first character must be a letter.
+ *
+ * @see Expr
+ * @see StreamTokenizer
+ * @author Lennart Andersson
+ */
+public class ExprParser {
+
+ private int token;
+ private StreamTokenizer tokenizer;
+
+ /**
+ * The build method returns an Expr representation of the expression
+ * provided by reader.
+ *
+ * @param reader a Reader provided the string to be parsed.
+ * @return an Expr representation of the string.
+ * @exception IOException if the reader does not deliver data.
+ * @exception ExprParserException if the reader input violates the grammar.
+ */
+ public Expr build(Reader reader) throws IOException {
+ tokenizer = new StreamTokenizer(reader);
+ tokenizer.ordinaryChar('-');
+ tokenizer.ordinaryChar('/');
+ token = tokenizer.nextToken();
+ Expr e = expr();
+ if (token == StreamTokenizer.TT_EOF) return e;
+ else throw new XLException("trailing garbage");
+ }
+
+ /**
+ * The build method returns an Expr representation of the expression
+ * provided by the input string.
+ *
+ * @param input the String to be parsed.
+ * @return an Expr representation of the string.
+ * @exception IOException if the input does not deliver data.
+ * @exception XLException if the input violates the grammar.
+ */
+ public Expr build(String input) throws IOException {
+ return build(new StringReader(input));
+ }
+
+ private Expr expr() throws IOException {
+ Expr result, term;
+ result = term();
+ while (token == '+' || token == '-') {
+ int op = token;
+ token = tokenizer.nextToken();
+ term = term();
+ switch (op) {
+ case '+':
+ result = new Add(result, term);
+ break;
+ case '-':
+ result = new Sub(result, term);
+ break;
+ }
+ }
+ return result;
+ }
+
+ private Expr factor() throws IOException {
+ Expr e;
+ switch (token) {
+ case '(':
+ token = tokenizer.nextToken();
+ e = expr();
+ if (token != ')') throw new XLException("expecting \")\", found: " + token);
+ token = tokenizer.nextToken();
+ return e;
+ case StreamTokenizer.TT_NUMBER:
+ double x = tokenizer.nval;
+ token = tokenizer.nextToken();
+ return new Num(x);
+ case StreamTokenizer.TT_WORD:
+ String address = tokenizer.sval.toUpperCase();
+ if (!Pattern.matches("[A-Z][0-9]+", address))
+ throw new XLException("illegal address: " + address);
+ token = tokenizer.nextToken();
+ return new Variable(address);
+ case StreamTokenizer.TT_EOF:
+ throw new XLException("unexpected end of text");
+ default:
+ throw new XLException("unexpected " + (char) token);
+ }
+ }
+
+ private Expr term() throws IOException {
+ Expr result, factor;
+ result = factor();
+ while (token == '*' || token == '/') {
+ int op = token;
+ token = tokenizer.nextToken();
+ factor = factor();
+ switch (op) {
+ case '*':
+ result = new Mul(result, factor);
+ break;
+ case '/':
+ result = new Div(result, factor);
+ break;
+ }
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/expr/Mul.java b/app/src/main/java/expr/Mul.java
new file mode 100644
index 0000000..b5e2f24
--- /dev/null
+++ b/app/src/main/java/expr/Mul.java
@@ -0,0 +1,18 @@
+package expr;
+
+class Mul extends BinaryExpr {
+
+ Mul(Expr expr1, Expr expr2) {
+ super(expr1, expr2);
+ precedence1 = 1;
+ precedence2 = 1;
+ }
+
+ public double op(double op1, double op2) {
+ return op1 * op2;
+ }
+
+ protected String opString() {
+ return "*";
+ }
+}
diff --git a/app/src/main/java/expr/Num.java b/app/src/main/java/expr/Num.java
new file mode 100644
index 0000000..e724043
--- /dev/null
+++ b/app/src/main/java/expr/Num.java
@@ -0,0 +1,21 @@
+package expr;
+
+import util.NumberAdjustment;
+
+class Num extends Expr {
+
+ private static NumberAdjustment adjustment = new NumberAdjustment(0, 2);
+ private double value;
+
+ Num(double value) {
+ this.value = value;
+ }
+
+ public String toString(int prec) {
+ return adjustment.right(value);
+ }
+
+ public double value(Environment env) {
+ return value;
+ }
+}
diff --git a/app/src/main/java/expr/Sub.java b/app/src/main/java/expr/Sub.java
new file mode 100644
index 0000000..80d8141
--- /dev/null
+++ b/app/src/main/java/expr/Sub.java
@@ -0,0 +1,18 @@
+package expr;
+
+class Sub extends BinaryExpr {
+
+ Sub(Expr expr1, Expr expr2) {
+ super(expr1, expr2);
+ precedence1 = 0;
+ precedence2 = 1;
+ }
+
+ public double op(double op1, double op2) {
+ return op1 - op2;
+ }
+
+ protected String opString() {
+ return "-";
+ }
+}
diff --git a/app/src/main/java/expr/TestExpr.java b/app/src/main/java/expr/TestExpr.java
new file mode 100644
index 0000000..62765a3
--- /dev/null
+++ b/app/src/main/java/expr/TestExpr.java
@@ -0,0 +1,30 @@
+package expr;
+
+import java.io.IOException;
+
+public class TestExpr {
+
+ public static void main(String[] args) {
+ ExprParser parser = new ExprParser();
+ try {
+ Expr expr = parser.build("1+2*3");
+ System.out.println(expr);
+ System.out.println(expr.value(null));
+ expr = parser.build("A3+A2*A1");
+ Environment env =
+ new Environment() {
+ public double value(String name) {
+ if (name.equals("A3")) return 1;
+ if (name.equals("A2")) return 2;
+ if (name.equals("A1")) return 3;
+ System.out.println(name + " is undefined");
+ return 0;
+ }
+ };
+ System.out.println(expr);
+ System.out.println(expr.value(env));
+ } catch (IOException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+}
diff --git a/app/src/main/java/expr/Variable.java b/app/src/main/java/expr/Variable.java
new file mode 100644
index 0000000..f96075e
--- /dev/null
+++ b/app/src/main/java/expr/Variable.java
@@ -0,0 +1,18 @@
+package expr;
+
+class Variable extends Expr {
+
+ private String name;
+
+ Variable(String name) {
+ this.name = name;
+ }
+
+ public String toString(int prec) {
+ return name.toString();
+ }
+
+ public double value(Environment env) {
+ return env.value(name);
+ }
+}
diff --git a/app/src/main/java/gui/BorderPanel.java b/app/src/main/java/gui/BorderPanel.java
new file mode 100644
index 0000000..85c62e0
--- /dev/null
+++ b/app/src/main/java/gui/BorderPanel.java
@@ -0,0 +1,13 @@
+package gui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import javax.swing.JPanel;
+
+public class BorderPanel extends JPanel {
+
+ protected BorderPanel() {
+ super(new BorderLayout(2, 2));
+ setBackground(Color.BLACK);
+ }
+}
diff --git a/app/src/main/java/gui/ColoredLabel.java b/app/src/main/java/gui/ColoredLabel.java
new file mode 100644
index 0000000..128f63a
--- /dev/null
+++ b/app/src/main/java/gui/ColoredLabel.java
@@ -0,0 +1,22 @@
+package gui;
+
+import java.awt.Color;
+import javax.swing.JLabel;
+import javax.swing.SwingConstants;
+
+public class ColoredLabel extends JLabel {
+
+ public ColoredLabel(String text) {
+ this(text, Color.WHITE, SwingConstants.LEFT);
+ }
+
+ public ColoredLabel(String text, Color color) {
+ this(text, color, SwingConstants.LEFT);
+ }
+
+ public ColoredLabel(String text, Color color, int alignment) {
+ super(text, alignment);
+ setBackground(color);
+ setOpaque(true);
+ }
+}
diff --git a/app/src/main/java/gui/CurrentLabel.java b/app/src/main/java/gui/CurrentLabel.java
new file mode 100644
index 0000000..525a488
--- /dev/null
+++ b/app/src/main/java/gui/CurrentLabel.java
@@ -0,0 +1,10 @@
+package gui;
+
+import java.awt.Color;
+
+public class CurrentLabel extends ColoredLabel {
+
+ public CurrentLabel() {
+ super("A1", Color.WHITE);
+ }
+}
diff --git a/app/src/main/java/gui/Editor.java b/app/src/main/java/gui/Editor.java
new file mode 100644
index 0000000..ae7b4d8
--- /dev/null
+++ b/app/src/main/java/gui/Editor.java
@@ -0,0 +1,11 @@
+package gui;
+
+import java.awt.Color;
+import javax.swing.JTextField;
+
+public class Editor extends JTextField {
+
+ public Editor() {
+ setBackground(Color.WHITE);
+ }
+}
diff --git a/app/src/main/java/gui/GridPanel.java b/app/src/main/java/gui/GridPanel.java
new file mode 100644
index 0000000..b70ab60
--- /dev/null
+++ b/app/src/main/java/gui/GridPanel.java
@@ -0,0 +1,13 @@
+package gui;
+
+import java.awt.Color;
+import java.awt.GridLayout;
+import javax.swing.JPanel;
+
+public class GridPanel extends JPanel {
+
+ public GridPanel(int rows, int columns) {
+ super(new GridLayout(rows, columns, 2, 2));
+ setBackground(Color.BLACK);
+ }
+}
diff --git a/app/src/main/java/gui/RowLabels.java b/app/src/main/java/gui/RowLabels.java
new file mode 100644
index 0000000..82a4b15
--- /dev/null
+++ b/app/src/main/java/gui/RowLabels.java
@@ -0,0 +1,15 @@
+package gui;
+
+import static java.awt.Color.LIGHT_GRAY;
+import static javax.swing.SwingConstants.RIGHT;
+
+class RowLabels extends GridPanel {
+
+ RowLabels(int rows) {
+ super(rows + 1, 1);
+ add(new ColoredLabel("", LIGHT_GRAY, RIGHT));
+ for (int i = 1; i <= rows; i++) {
+ add(new ColoredLabel(String.valueOf(i), LIGHT_GRAY, RIGHT));
+ }
+ }
+}
diff --git a/app/src/main/java/gui/SheetPanel.java b/app/src/main/java/gui/SheetPanel.java
new file mode 100644
index 0000000..4f777e8
--- /dev/null
+++ b/app/src/main/java/gui/SheetPanel.java
@@ -0,0 +1,12 @@
+package gui;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.WEST;
+
+public class SheetPanel extends BorderPanel {
+
+ public SheetPanel(int rows, int columns) {
+ add(WEST, new RowLabels(rows));
+ add(CENTER, new SlotLabels(rows, columns));
+ }
+}
diff --git a/app/src/main/java/gui/SlotLabel.java b/app/src/main/java/gui/SlotLabel.java
new file mode 100644
index 0000000..5f26e9b
--- /dev/null
+++ b/app/src/main/java/gui/SlotLabel.java
@@ -0,0 +1,10 @@
+package gui;
+
+import java.awt.Color;
+
+public class SlotLabel extends ColoredLabel {
+
+ public SlotLabel() {
+ super(" ", Color.WHITE, RIGHT);
+ }
+}
diff --git a/app/src/main/java/gui/SlotLabels.java b/app/src/main/java/gui/SlotLabels.java
new file mode 100644
index 0000000..2423216
--- /dev/null
+++ b/app/src/main/java/gui/SlotLabels.java
@@ -0,0 +1,28 @@
+package gui;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.SwingConstants;
+
+public class SlotLabels extends GridPanel {
+
+ private List labelList;
+
+ public SlotLabels(int rows, int cols) {
+ super(rows + 1, cols);
+ labelList = new ArrayList(rows * cols);
+ for (char ch = 'A'; ch < 'A' + cols; ch++) {
+ add(new ColoredLabel(Character.toString(ch), Color.LIGHT_GRAY, SwingConstants.CENTER));
+ }
+ for (int row = 1; row <= rows; row++) {
+ for (char ch = 'A'; ch < 'A' + cols; ch++) {
+ SlotLabel label = new SlotLabel();
+ add(label);
+ labelList.add(label);
+ }
+ }
+ SlotLabel firstLabel = labelList.get(0);
+ firstLabel.setBackground(Color.YELLOW);
+ }
+}
diff --git a/app/src/main/java/gui/StatusLabel.java b/app/src/main/java/gui/StatusLabel.java
new file mode 100644
index 0000000..c902879
--- /dev/null
+++ b/app/src/main/java/gui/StatusLabel.java
@@ -0,0 +1,16 @@
+package gui;
+
+import java.awt.Color;
+import java.util.Observable;
+import java.util.Observer;
+
+public class StatusLabel extends ColoredLabel implements Observer {
+
+ public StatusLabel() {
+ super("", Color.WHITE);
+ }
+
+ public void update(Observable observable, Object object) {
+ setText("");
+ }
+}
diff --git a/app/src/main/java/gui/StatusPanel.java b/app/src/main/java/gui/StatusPanel.java
new file mode 100644
index 0000000..2c31617
--- /dev/null
+++ b/app/src/main/java/gui/StatusPanel.java
@@ -0,0 +1,12 @@
+package gui;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.WEST;
+
+public class StatusPanel extends BorderPanel {
+
+ protected StatusPanel(StatusLabel statusLabel) {
+ add(WEST, new CurrentLabel());
+ add(CENTER, statusLabel);
+ }
+}
diff --git a/app/src/main/java/gui/XL.java b/app/src/main/java/gui/XL.java
new file mode 100644
index 0000000..6dc9079
--- /dev/null
+++ b/app/src/main/java/gui/XL.java
@@ -0,0 +1,49 @@
+package gui;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import gui.menu.XLMenuBar;
+
+public class XL extends JFrame {
+
+ private static final int ROWS = 10, COLUMNS = 8;
+ private XLCounter counter;
+ private StatusLabel statusLabel = new StatusLabel();
+ private XLList xlList;
+
+ public XL(XL oldXL) {
+ this(oldXL.xlList, oldXL.counter);
+ }
+
+ public XL(XLList xlList, XLCounter counter) {
+ super("Untitled-" + counter);
+ this.xlList = xlList;
+ this.counter = counter;
+ xlList.add(this);
+ counter.increment();
+ JPanel statusPanel = new StatusPanel(statusLabel);
+ JPanel sheetPanel = new SheetPanel(ROWS, COLUMNS);
+ Editor editor = new Editor();
+ add(NORTH, statusPanel);
+ add(CENTER, editor);
+ add(SOUTH, sheetPanel);
+ setJMenuBar(new XLMenuBar(this, xlList, statusLabel));
+ pack();
+ setDefaultCloseOperation(EXIT_ON_CLOSE);
+ setResizable(false);
+ setVisible(true);
+ }
+
+ public void rename(String title) {
+ setTitle(title);
+ xlList.setChanged();
+ }
+
+ public static void main(String[] args) {
+ new XL(new XLList(), new XLCounter());
+ }
+}
diff --git a/app/src/main/java/gui/XLCounter.java b/app/src/main/java/gui/XLCounter.java
new file mode 100644
index 0000000..7bf609a
--- /dev/null
+++ b/app/src/main/java/gui/XLCounter.java
@@ -0,0 +1,14 @@
+package gui;
+
+public class XLCounter {
+
+ private int counter;
+
+ public void increment() {
+ counter++;
+ }
+
+ public String toString() {
+ return Integer.toString(counter);
+ }
+}
diff --git a/app/src/main/java/gui/XLList.java b/app/src/main/java/gui/XLList.java
new file mode 100644
index 0000000..7a937ef
--- /dev/null
+++ b/app/src/main/java/gui/XLList.java
@@ -0,0 +1,40 @@
+package gui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Observable;
+
+public class XLList extends Observable implements Iterable {
+
+ private List list = new ArrayList();
+
+ public void add(XL xl) {
+ list.add(xl);
+ setChanged();
+ notifyObservers();
+ }
+
+ public boolean isEmpty() {
+ return list.isEmpty();
+ }
+
+ public Iterator iterator() {
+ return list.iterator();
+ }
+
+ public XL last() {
+ return list.get(list.size() - 1);
+ }
+
+ public void remove(XL xl) {
+ list.remove(xl);
+ setChanged();
+ notifyObservers();
+ }
+
+ public void setChanged() {
+ super.setChanged();
+ notifyObservers();
+ }
+}
diff --git a/app/src/main/java/gui/menu/ClearAllMenuItem.java b/app/src/main/java/gui/menu/ClearAllMenuItem.java
new file mode 100644
index 0000000..ca9a04c
--- /dev/null
+++ b/app/src/main/java/gui/menu/ClearAllMenuItem.java
@@ -0,0 +1,17 @@
+package gui.menu;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JMenuItem;
+
+class ClearAllMenuItem extends JMenuItem implements ActionListener {
+
+ public ClearAllMenuItem() {
+ super("Clear all");
+ addActionListener(this);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ // TODO
+ }
+}
diff --git a/app/src/main/java/gui/menu/ClearMenuItem.java b/app/src/main/java/gui/menu/ClearMenuItem.java
new file mode 100644
index 0000000..f3e85a8
--- /dev/null
+++ b/app/src/main/java/gui/menu/ClearMenuItem.java
@@ -0,0 +1,17 @@
+package gui.menu;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JMenuItem;
+
+class ClearMenuItem extends JMenuItem implements ActionListener {
+
+ public ClearMenuItem() {
+ super("Clear");
+ addActionListener(this);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ // TODO
+ }
+}
diff --git a/app/src/main/java/gui/menu/CloseMenuItem.java b/app/src/main/java/gui/menu/CloseMenuItem.java
new file mode 100644
index 0000000..ebb1225
--- /dev/null
+++ b/app/src/main/java/gui/menu/CloseMenuItem.java
@@ -0,0 +1,30 @@
+package gui.menu;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JMenuItem;
+import gui.XL;
+import gui.XLList;
+
+class CloseMenuItem extends JMenuItem implements ActionListener {
+
+ private XL xl;
+ private XLList xlList;
+
+ public CloseMenuItem(XL xl, XLList xlList) {
+ super("Close");
+ this.xl = xl;
+ this.xlList = xlList;
+ addActionListener(this);
+ }
+
+ public void actionPerformed(ActionEvent event) {
+ xlList.remove(xl);
+ xl.dispose();
+ if (xlList.isEmpty()) {
+ System.exit(0);
+ } else {
+ xlList.last().toFront();
+ }
+ }
+}
diff --git a/app/src/main/java/gui/menu/LoadMenuItem.java b/app/src/main/java/gui/menu/LoadMenuItem.java
new file mode 100644
index 0000000..a8ccfb3
--- /dev/null
+++ b/app/src/main/java/gui/menu/LoadMenuItem.java
@@ -0,0 +1,21 @@
+package gui.menu;
+
+import java.io.FileNotFoundException;
+import javax.swing.JFileChooser;
+import gui.StatusLabel;
+import gui.XL;
+
+class LoadMenuItem extends OpenMenuItem {
+
+ public LoadMenuItem(XL xl, StatusLabel statusLabel) {
+ super(xl, statusLabel, "Load");
+ }
+
+ protected void action(String path) throws FileNotFoundException {
+ // TODO
+ }
+
+ protected int openDialog(JFileChooser fileChooser) {
+ return fileChooser.showOpenDialog(xl);
+ }
+}
diff --git a/app/src/main/java/gui/menu/NewMenuItem.java b/app/src/main/java/gui/menu/NewMenuItem.java
new file mode 100644
index 0000000..c5cc4cc
--- /dev/null
+++ b/app/src/main/java/gui/menu/NewMenuItem.java
@@ -0,0 +1,21 @@
+package gui.menu;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JMenuItem;
+import gui.XL;
+
+class NewMenuItem extends JMenuItem implements ActionListener {
+
+ private XL xl;
+
+ public NewMenuItem(XL xl) {
+ super("New");
+ this.xl = xl;
+ addActionListener(this);
+ }
+
+ public void actionPerformed(ActionEvent event) {
+ new XL(xl);
+ }
+}
diff --git a/app/src/main/java/gui/menu/OpenMenuItem.java b/app/src/main/java/gui/menu/OpenMenuItem.java
new file mode 100644
index 0000000..a3c14a5
--- /dev/null
+++ b/app/src/main/java/gui/menu/OpenMenuItem.java
@@ -0,0 +1,45 @@
+package gui.menu;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileNotFoundException;
+import javax.swing.JFileChooser;
+import javax.swing.JMenuItem;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import gui.StatusLabel;
+import gui.XL;
+
+public abstract class OpenMenuItem extends JMenuItem implements ActionListener {
+
+ protected StatusLabel statusLabel;
+ protected XL xl;
+
+ protected OpenMenuItem(XL xl, StatusLabel statusLabel, String title) {
+ super(title);
+ this.xl = xl;
+ this.statusLabel = statusLabel;
+ addActionListener(this);
+ }
+
+ protected abstract void action(String path) throws FileNotFoundException;
+
+ public void actionPerformed(ActionEvent event) {
+ JFileChooser fileChooser = new JFileChooser(".");
+ FileFilter filter = new FileNameExtensionFilter("XL files", "xl");
+ fileChooser.setFileFilter(filter);
+ int option = openDialog(fileChooser);
+ if (option == JFileChooser.APPROVE_OPTION) {
+ File file = fileChooser.getSelectedFile();
+ try {
+ action(file.toString());
+ xl.rename(file.getName());
+ } catch (FileNotFoundException e) {
+ statusLabel.setText(e.getMessage());
+ }
+ }
+ }
+
+ protected abstract int openDialog(JFileChooser fileChooser);
+}
diff --git a/app/src/main/java/gui/menu/SaveMenuItem.java b/app/src/main/java/gui/menu/SaveMenuItem.java
new file mode 100644
index 0000000..169a08e
--- /dev/null
+++ b/app/src/main/java/gui/menu/SaveMenuItem.java
@@ -0,0 +1,21 @@
+package gui.menu;
+
+import java.io.FileNotFoundException;
+import javax.swing.JFileChooser;
+import gui.StatusLabel;
+import gui.XL;
+
+class SaveMenuItem extends OpenMenuItem {
+
+ public SaveMenuItem(XL xl, StatusLabel statusLabel) {
+ super(xl, statusLabel, "Save");
+ }
+
+ protected void action(String path) throws FileNotFoundException {
+ // TODO
+ }
+
+ protected int openDialog(JFileChooser fileChooser) {
+ return fileChooser.showSaveDialog(xl);
+ }
+}
diff --git a/app/src/main/java/gui/menu/WindowMenu.java b/app/src/main/java/gui/menu/WindowMenu.java
new file mode 100644
index 0000000..2b8b375
--- /dev/null
+++ b/app/src/main/java/gui/menu/WindowMenu.java
@@ -0,0 +1,26 @@
+package gui.menu;
+
+import java.util.Observable;
+import java.util.Observer;
+import javax.swing.JMenu;
+import gui.XL;
+import gui.XLList;
+
+public class WindowMenu extends JMenu implements Observer {
+
+ private XLList xlList;
+
+ public WindowMenu(XLList xlList) {
+ super("Window");
+ this.xlList = xlList;
+ xlList.addObserver(this);
+ update(null, null);
+ }
+
+ public void update(Observable observable, Object object) {
+ removeAll();
+ for (XL xl : xlList) {
+ add(new WindowMenuItem(xl));
+ }
+ }
+}
diff --git a/app/src/main/java/gui/menu/WindowMenuItem.java b/app/src/main/java/gui/menu/WindowMenuItem.java
new file mode 100644
index 0000000..0ffd3d8
--- /dev/null
+++ b/app/src/main/java/gui/menu/WindowMenuItem.java
@@ -0,0 +1,21 @@
+package gui.menu;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JMenuItem;
+import gui.XL;
+
+class WindowMenuItem extends JMenuItem implements ActionListener {
+
+ private XL xl;
+
+ public WindowMenuItem(XL xl) {
+ super(xl.getTitle());
+ this.xl = xl;
+ addActionListener(this);
+ }
+
+ public void actionPerformed(ActionEvent event) {
+ xl.toFront();
+ }
+}
diff --git a/app/src/main/java/gui/menu/XLMenuBar.java b/app/src/main/java/gui/menu/XLMenuBar.java
new file mode 100644
index 0000000..25bed14
--- /dev/null
+++ b/app/src/main/java/gui/menu/XLMenuBar.java
@@ -0,0 +1,24 @@
+package gui.menu;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import gui.StatusLabel;
+import gui.XL;
+import gui.XLList;
+
+public class XLMenuBar extends JMenuBar {
+
+ public XLMenuBar(XL xl, XLList xlList, StatusLabel statusLabel) {
+ JMenu file = new JMenu("File");
+ JMenu edit = new JMenu("Edit");
+ file.add(new SaveMenuItem(xl, statusLabel));
+ file.add(new LoadMenuItem(xl, statusLabel));
+ file.add(new NewMenuItem(xl));
+ file.add(new CloseMenuItem(xl, xlList));
+ edit.add(new ClearMenuItem());
+ edit.add(new ClearAllMenuItem());
+ add(file);
+ add(edit);
+ add(new WindowMenu(xlList));
+ }
+}
diff --git a/app/src/main/java/util/Adjustment.java b/app/src/main/java/util/Adjustment.java
new file mode 100644
index 0000000..96b06f0
--- /dev/null
+++ b/app/src/main/java/util/Adjustment.java
@@ -0,0 +1,134 @@
+package util;
+
+/**
+ * Adjustment.java Created: Tue Oct 24 2005
+ *
+ * @author Lennart Andersson
+ * @version 0.1
+ */
+/** Adjustment is a class for adjusting string representations of values within a String. */
+public class Adjustment {
+
+ private int width;
+
+ /**
+ * Creates an adjustment.
+ *
+ * @param width is the number of positions for the result. If the width is insufficient extra
+ * positions are added.
+ */
+ public Adjustment(int width) {
+ this.width = width;
+ }
+
+ /**
+ * Returns a centered String.
+ *
+ * @param s is the string to adjust.
+ */
+ public String center(String s) {
+ StringBuilder builder = new StringBuilder(width);
+ int fill = width - s.length();
+ for (int i = 0; i < fill / 2; i++) {
+ builder.append(' ');
+ }
+ builder.append(s);
+ for (int i = 0; i < fill - fill / 2; i++) {
+ builder.append(' ');
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Returns a left adjusted String.
+ *
+ * @param s is the string to adjust.
+ */
+ public String left(String s) {
+ StringBuilder builder = new StringBuilder(width);
+ builder.append(s);
+ int fill = width - s.length();
+ for (int i = 0; i < fill; i++) {
+ builder.append(' ');
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Returns a right adjusted String.
+ *
+ * @param b is the value to adjust.
+ */
+ public String right(boolean b) {
+ return right(String.valueOf(b));
+ }
+
+ /**
+ * Returns a right adjusted String.
+ *
+ * @param c is the value to adjust.
+ */
+ public String right(char c) {
+ return right(String.valueOf(c));
+ }
+
+ /**
+ * Returns a right adjusted String.
+ *
+ * @param number is the value to adjust.
+ */
+ public String right(double number) {
+ return right(String.valueOf(number));
+ }
+
+ /**
+ * Returns a right adjusted String.
+ *
+ * @param number is the value to adjust.
+ */
+ public String right(float number) {
+ return right(String.valueOf(number));
+ }
+
+ /**
+ * Returns a right adjusted String.
+ *
+ * @param number is the value to adjust.
+ */
+ public String right(int number) {
+ return right(String.valueOf(number));
+ }
+
+ /**
+ * Returns a right adjusted String.
+ *
+ * @param number is the value to adjust.
+ */
+ public String right(long number) {
+ return right(String.valueOf(number));
+ }
+
+ /**
+ * Returns a right adjusted String.
+ *
+ * @param item is the value to adjust.
+ */
+ public String right(Object item) {
+ return right(String.valueOf(item));
+ }
+
+ /**
+ * Returns a right adjusted String.
+ *
+ * @param s is the string to adjust.
+ */
+ public String right(String s) {
+ StringBuilder builder = new StringBuilder(width);
+ int fill = width - s.length();
+ for (int i = 0; i < fill; i++) {
+ builder.append(' ');
+ }
+ builder.append(s);
+ return builder.toString();
+ }
+}
diff --git a/app/src/main/java/util/NumberAdjustment.java b/app/src/main/java/util/NumberAdjustment.java
new file mode 100644
index 0000000..5e5db88
--- /dev/null
+++ b/app/src/main/java/util/NumberAdjustment.java
@@ -0,0 +1,123 @@
+package util;
+
+/**
+ * NumberAdjustment.java Created: Mon OTue 24 2005
+ *
+ * @author Lennart Andersson
+ * @version 0.1
+ */
+/**
+ * Adjustment is a class for adjusting string representations of numerical values within a String.
+ */
+public class NumberAdjustment extends Adjustment {
+
+ private static final double log10 = Math.log(10.0);
+ private int decimals;
+ private int exponent;
+
+ /**
+ * Creates an adjustment for numbers.
+ *
+ * @param width is the number of positions for the result. If the width is insufficient extra
+ * positions are added.
+ * @param decimals is the number of decimals in the result.
+ */
+ public NumberAdjustment(int width, int decimals) {
+ super(width);
+ this.decimals = decimals;
+ }
+
+ /**
+ * Creates an adjustment for numbers with an exponent field.
+ *
+ * @param width is the number of positions for the result. If the width is insufficient extra
+ * positions are added.
+ * @param decimals is the number of positions for the decimals.
+ * @param exponent is the number of positions for the exponent including the letter E. If the
+ * width is unsufficient extra positions are added.
+ */
+ public NumberAdjustment(int width, int decimals, int exponent) {
+ this(width, decimals);
+ this.exponent = exponent;
+ }
+
+ private StringBuilder fillZero(int exp) {
+ StringBuilder builder = new StringBuilder();
+ int length = exponent;
+ if (exp < 0) {
+ builder.append('-');
+ exp = -exp;
+ length = exponent - 1;
+ }
+ String s = String.valueOf(exp);
+ for (int i = s.length(); i < length; i++) {
+ builder.append('0');
+ }
+ return builder.append(s);
+ }
+
+ private StringBuilder format(double number) {
+ StringBuilder builder = new StringBuilder();
+ long intpart = (long) number;
+ builder.append(intpart);
+ double fraction = number - intpart;
+ if (decimals > 0) {
+ builder.append('.');
+ }
+ for (int i = 0; i < decimals; i++) {
+ fraction *= 10;
+ int d = (int) fraction;
+ builder.append((char) (d + '0'));
+ fraction = fraction - d;
+ }
+ return builder;
+ }
+
+ /**
+ * Returns a right adjusted String.
+ *
+ * @param number is the value to adjust.
+ */
+ public String right(double number) {
+ StringBuilder builder = new StringBuilder();
+ boolean negative = number < 0;
+ if (negative) {
+ number = -number;
+ builder.append('-');
+ }
+ if (exponent > 0) {
+ if (number == 0.0) return right(format(0.0) + "E" + fillZero(0));
+ int exp = (int) Math.floor((Math.log(number) / log10));
+ number /= Math.pow(10.0, exp);
+ number += 0.5 * Math.pow(10.0, -decimals);
+ if (number >= 10.0) {
+ number /= 10.0;
+ exp++;
+ }
+ builder.append(format(number)).append('E').append(fillZero(exp));
+ } else {
+ number += 0.5 * Math.pow(10.0, -decimals);
+ builder.append(format(number));
+ }
+ return right(builder.toString());
+ }
+
+ /**
+ * Returns a right adjusted String.
+ *
+ * @param number is the value to adjust.
+ */
+ public String right(float number) {
+ return right((double) number);
+ }
+
+ public static void main(String[] args) {
+ Adjustment adjustment = new NumberAdjustment(10, 2, 2);
+ System.out.println("0123456789");
+ System.out.println(adjustment.right(-0.0000000000000000000000000));
+ float value = (float) (1 / 3.0);
+ System.out.println(adjustment.right(value));
+ adjustment = new Adjustment(12);
+ System.out.println(adjustment.right(value));
+ }
+}
diff --git a/app/src/main/java/util/XLBufferedReader.java b/app/src/main/java/util/XLBufferedReader.java
new file mode 100644
index 0000000..308b223
--- /dev/null
+++ b/app/src/main/java/util/XLBufferedReader.java
@@ -0,0 +1,27 @@
+package util;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.util.Map;
+
+// TODO move to another package
+public class XLBufferedReader extends BufferedReader {
+
+ public XLBufferedReader(String name) throws FileNotFoundException {
+ super(new FileReader(name));
+ }
+
+ // TODO Change Object to something appropriate
+ public void load(Map map) {
+ try {
+ while (ready()) {
+ String string = readLine();
+ int i = string.indexOf('=');
+ // TODO
+ }
+ } catch (Exception e) {
+ throw new XLException(e.getMessage());
+ }
+ }
+}
diff --git a/app/src/main/java/util/XLException.java b/app/src/main/java/util/XLException.java
new file mode 100644
index 0000000..76cb9ea
--- /dev/null
+++ b/app/src/main/java/util/XLException.java
@@ -0,0 +1,8 @@
+package util;
+
+public class XLException extends RuntimeException {
+
+ public XLException(String message) {
+ super(message);
+ }
+}
diff --git a/app/src/main/java/util/XLPrintStream.java b/app/src/main/java/util/XLPrintStream.java
new file mode 100644
index 0000000..1490357
--- /dev/null
+++ b/app/src/main/java/util/XLPrintStream.java
@@ -0,0 +1,25 @@
+package util;
+
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+import java.util.Map.Entry;
+import java.util.Set;
+
+// TODO move to another package
+public class XLPrintStream extends PrintStream {
+
+ public XLPrintStream(String fileName) throws FileNotFoundException {
+ super(fileName);
+ }
+
+ // TODO Change Object to something appropriate
+ public void save(Set> set) {
+ for (Entry entry : set) {
+ print(entry.getKey());
+ print('=');
+ println(entry.getValue());
+ }
+ flush();
+ close();
+ }
+}
diff --git a/app/src/test/resources/fibonacci.xl b/app/src/test/resources/fibonacci.xl
new file mode 100644
index 0000000..6173375
--- /dev/null
+++ b/app/src/test/resources/fibonacci.xl
@@ -0,0 +1,32 @@
+A1=1.00
+A2=1.00
+A3=A1+A2
+A4=A2+A3
+A5=A3+A4
+A6=A4+A5
+A7=A5+A6
+A8=A6+A7
+B1=A7+A8
+B2=A8+B1
+B3=B1+B2
+B4=B2+B3
+B5=B3+B4
+B6=B4+B5
+B7=B5+B6
+B8=B6+B7
+C1=B7+B8
+C2=B8+C1
+C3=C1+C2
+C4=C2+C3
+C5=C3+C4
+C6=C4+C5
+C7=C5+C6
+C8=C6+C7
+D1=C7+C8
+D2=C8+D1
+D3=D1+D2
+D4=D2+D3
+D5=D3+D4
+D6=D4+D5
+D7=D5+D6
+D8=D6+D7
diff --git a/app/src/test/resources/manual.xl b/app/src/test/resources/manual.xl
new file mode 100644
index 0000000..ed3069b
--- /dev/null
+++ b/app/src/test/resources/manual.xl
@@ -0,0 +1,6 @@
+A2=#y =
+B1=2.00
+B2=3.00
+B3=B1*B2
+A3=#x*y =
+A1=#x =
diff --git a/app/src/test/resources/omvandmanual.xl b/app/src/test/resources/omvandmanual.xl
new file mode 100644
index 0000000..b64f096
--- /dev/null
+++ b/app/src/test/resources/omvandmanual.xl
@@ -0,0 +1,6 @@
+A1=#x*y =
+A2=#x =
+A3=#y =
+B1=B2*B3
+B2=2.00
+B3=3.00
diff --git a/doc/manual.pdf b/doc/manual.pdf
new file mode 100644
index 0000000..c202b61
Binary files /dev/null and b/doc/manual.pdf differ
diff --git a/doc/overview.pdf b/doc/overview.pdf
new file mode 100644
index 0000000..c6dc499
Binary files /dev/null and b/doc/overview.pdf differ
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..2e45223
--- /dev/null
+++ b/makefile
@@ -0,0 +1,38 @@
+GITHASH := $(shell git rev-parse --short HEAD)$(shell git diff-index --quiet HEAD || echo "-dirty")
+
+TARNAME := xl-imbus-$(GITHASH).tar.gz
+
+run:
+ echo $(GITHASH)
+ ./gradlew run
+
+watch:
+ ./gradlew run --continuous
+
+build:
+ ./gradlew build
+ #./gradlew shadowJar
+
+clean:
+ ./gradlew clean
+ rm -f *.tar.gz *.tar.gz.minisig *.zip *.jpg
+
+test:
+ ./gradlew test
+
+$(TARNAME):
+ git ls-files -z | xargs -0 tar -czf $(TARNAME)
+
+$(TARNAME).minisig: $(TARNAME)
+ minisign -Sm $(TARNAME)
+
+tar: $(TARNAME)
+ tar -tvf $(TARNAME)
+
+sign: $(TARNAME).minisig
+
+publish: $(TARNAME) $(TARNAME).minisig
+ scp $(TARNAME) server:/public/xl/$(TARNAME)
+ scp $(TARNAME).minisig server:/public/xl/$(TARNAME).minisig
+
+.PHONY: run watch build clean test archive sign publish