Compare commits

...

6 commits

Author SHA1 Message Date
Imbus
3b0eb9052d Controller first working version 2024-06-03 19:33:14 +02:00
Imbus
adbdfd3b99 Gui/View extensive changes 2024-06-03 19:32:41 +02:00
Imbus
7b3ddf17e0 Utility classes 2024-06-03 19:31:56 +02:00
Imbus
a56a3be9ef Model initial draft 2024-06-03 19:31:45 +02:00
Imbus
81e22d094b Menu proper implementation 2024-06-03 19:31:27 +02:00
Imbus
4b47ea8d59 Initial model implementation 2024-06-03 19:21:42 +02:00
28 changed files with 630 additions and 95 deletions

View file

@ -0,0 +1,59 @@
package xl.controller;
import xl.model.XLModel;
import xl.gui.Editor;
import xl.gui.SlotLabel;
import xl.gui.StatusLabel;
/**
* The controller for the editor.
*
* The controller is responsible for handling the editor actions.
*/
public class EditorController {
private XLModel xlModel;
private Editor editor;
private SlotLabel currentSlot;
private StatusLabel statusLabel;
public EditorController(XLModel xlModel, StatusLabel statusLabel) {
this.xlModel = xlModel;
this.statusLabel = statusLabel;
}
public void setCurrentSlot(SlotLabel currentSlot) {
this.currentSlot = currentSlot;
}
public void setEditor(Editor editor) {
this.editor = editor;
}
public void handleEditorAction() {
String content = editor.getText();
String address = currentSlot.getAddress();
if (content.isEmpty()) {
boolean isOkRemove = xlModel.remove(address);
if (isOkRemove) {
currentSlot.setText("");
statusLabel.setText("");
} else {
statusLabel.setText(xlModel.getStatus());
}
} else if (content.charAt(0) == '#') {
xlModel.add(address, content);
currentSlot.setText(content.substring(1));
statusLabel.setText("");
} else {
xlModel.add(address, content);
if (xlModel.getStatus().equals("")) {
content = xlModel.getAddressValue(address);
currentSlot.setText(content);
} else {
statusLabel.setText(xlModel.getStatus());
}
}
}
}

View file

@ -0,0 +1,60 @@
package xl.controller;
import java.awt.Color;
import xl.model.XLModel;
import xl.gui.*;
/**
* The controller for the slot labels.
*
* The controller is responsible for handling the slot label actions.
*/
public class SlotLabelController {
private XLModel xlModel;
private Editor editor;
private SlotLabel currentSlot;
private EditorController editorController;
private StatusLabel statusLabel;
private CurrentLabel currentLabel;
public SlotLabelController(XLModel xlModel, Editor editor, StatusLabel statusLabel) {
this.xlModel = xlModel;
this.editor = editor;
this.statusLabel = statusLabel;
}
public void setCurrentLabel(CurrentLabel currentLabel) {
this.currentLabel = currentLabel;
}
public void setCurrentSlot(SlotLabel currentSlot) {
this.currentSlot = currentSlot;
}
public void setEditorController(EditorController editorController) {
this.editorController = editorController;
}
public void handleSlotClick(SlotLabel slotLabel) {
// Retrieve the address of the clicked slot
String address = slotLabel.getAddress();
currentLabel.setText(address);
xlModel.setCurrentAddress(address);
// Clear the status label
statusLabel.setText("");
// Reset the background color of the previous slot
currentSlot.setBackground(Color.WHITE);
// Set the background color of the clicked slot
slotLabel.setBackground(Color.YELLOW);
currentSlot = slotLabel;
editorController.setCurrentSlot(currentSlot);
// Retrieve the content of the clicked slot
String content = xlModel.getAddressContent(address);
// Set the content of the clicked slot in the editor
editor.setText(content);
}
}

View file

@ -3,9 +3,21 @@ package xl.gui;
import java.awt.Color;
import javax.swing.JTextField;
public class Editor extends JTextField {
import xl.controller.EditorController;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public Editor() {
public class Editor extends JTextField {
public Editor(EditorController editorController) {
setBackground(Color.WHITE);
// Listen for changes in the text
addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
editorController.handleEditorAction();
}
});
}
}

View file

@ -3,10 +3,15 @@ package xl.gui;
import static java.awt.BorderLayout.CENTER;
import static java.awt.BorderLayout.WEST;
import xl.controller.EditorController;
import xl.controller.SlotLabelController;
import xl.model.XLModel;
public class SheetPanel extends BorderPanel {
public SheetPanel(int rows, int columns) {
public SheetPanel(int rows, int columns, XLModel xlModel, SlotLabelController slotLabelController,
EditorController editorController) {
add(WEST, new RowLabels(rows));
add(CENTER, new SlotLabels(rows, columns));
add(CENTER, new SlotLabels(rows, columns, xlModel, slotLabelController, editorController));
}
}

View file

@ -1,10 +1,45 @@
package xl.gui;
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import xl.controller.SlotLabelController;
import java.util.Observer;
import java.util.Observable;
import xl.model.XLModel;
public class SlotLabel extends ColoredLabel {
public class SlotLabel extends ColoredLabel implements Observer {
private String address;
public SlotLabel() {
public SlotLabel(SlotLabelController slotLabelController, int row, char column) {
super(" ", Color.WHITE, RIGHT);
this.address = "" + column + row;
// MouseListener for mouse clicks
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
slotLabelController.handleSlotClick(SlotLabel.this);
}
});
}
public String getAddress() {
return address;
}
public void update(Observable o, Object arg) {
if (o instanceof XLModel) {
XLModel xlModel = (XLModel) o;
if (xlModel.containsKey(address)) {
String tempCurrentAdress = xlModel.getAddressContent(address);
xlModel.setCurrentAddress(address);
String content = xlModel.getAddressValue(address);
setText(content);
xlModel.setCurrentAddress(tempCurrentAdress);
} else {
setText("");
}
}
}
}

View file

@ -4,25 +4,35 @@ import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingConstants;
import xl.model.XLModel;
import xl.controller.EditorController;
import xl.controller.SlotLabelController;
public class SlotLabels extends GridPanel {
private List<SlotLabel> labelList;
public SlotLabels(int rows, int cols) {
public SlotLabels(int rows, int cols, XLModel xlModel, SlotLabelController slotLabelController,
EditorController editorController) {
super(rows + 1, cols);
labelList = new ArrayList<SlotLabel>(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();
SlotLabel label = new SlotLabel(slotLabelController, row, ch);
xlModel.addObserver(label);
add(label);
labelList.add(label);
}
}
SlotLabel firstLabel = labelList.get(0);
firstLabel.setBackground(Color.YELLOW);
xlModel.setCurrentAddress("A1");
slotLabelController.setCurrentSlot(firstLabel);
editorController.setCurrentSlot(firstLabel);
}
}

View file

@ -1,16 +1,10 @@
package xl.gui;
import java.awt.Color;
import java.util.Observable;
import java.util.Observer;
public class StatusLabel extends ColoredLabel implements Observer {
public class StatusLabel extends ColoredLabel {
public StatusLabel() {
super("", Color.WHITE);
}
public void update(Observable observable, Object object) {
setText("");
}
}

View file

@ -3,10 +3,15 @@ package xl.gui;
import static java.awt.BorderLayout.CENTER;
import static java.awt.BorderLayout.WEST;
import xl.controller.SlotLabelController;
import xl.model.XLModel;
public class StatusPanel extends BorderPanel {
protected StatusPanel(StatusLabel statusLabel) {
add(WEST, new CurrentLabel());
protected StatusPanel(StatusLabel statusLabel, XLModel solver, SlotLabelController slotLabelController) {
CurrentLabel currentLabel = new CurrentLabel();
slotLabelController.setCurrentLabel(currentLabel);
add(WEST, currentLabel);
add(CENTER, statusLabel);
}
}

View file

@ -6,7 +6,11 @@ import static java.awt.BorderLayout.SOUTH;
import javax.swing.JFrame;
import javax.swing.JPanel;
import xl.controller.EditorController;
import xl.controller.SlotLabelController;
import xl.gui.menu.XLMenuBar;
import xl.model.XLModel;
public class XL extends JFrame {
@ -15,23 +19,31 @@ public class XL extends JFrame {
private StatusLabel statusLabel = new StatusLabel();
private XLList xlList;
public XL(XL oldXL) {
this(oldXL.xlList, oldXL.counter);
public XL(XL oldXL, XLModel XLModel) {
this(oldXL.xlList, oldXL.counter, XLModel);
}
public XL(XLList xlList, XLCounter counter) {
super("Untitled-" + counter);
public XL(XLList xlList, XLCounter counter, XLModel XLModel) {
super("Window " + 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();
EditorController editorController = new EditorController(XLModel, statusLabel);
Editor editor = new Editor(editorController);
editorController.setEditor(editor);
SlotLabelController slotLabelController = new SlotLabelController(XLModel, editor, statusLabel);
slotLabelController.setEditorController(editorController);
JPanel statusPanel = new StatusPanel(statusLabel, XLModel, slotLabelController);
JPanel sheetPanel = new SheetPanel(ROWS, COLUMNS, XLModel, slotLabelController, editorController);
add(NORTH, statusPanel);
add(CENTER, editor);
add(SOUTH, sheetPanel);
setJMenuBar(new XLMenuBar(this, xlList, statusLabel));
setJMenuBar(new XLMenuBar(this, xlList, statusLabel, XLModel));
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
@ -44,6 +56,6 @@ public class XL extends JFrame {
}
public static void main(String[] args) {
new XL(new XLList(), new XLCounter());
new XL(new XLList(), new XLCounter(), new XLModel());
}
}

View file

@ -4,14 +4,18 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JMenuItem;
class ClearAllMenuItem extends JMenuItem implements ActionListener {
import xl.model.XLModel;
public ClearAllMenuItem() {
class ClearAllMenuItem extends JMenuItem implements ActionListener {
private XLModel xlModel;
public ClearAllMenuItem(XLModel xlModel) {
super("Clear all");
this.xlModel = xlModel;
addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
// TODO
xlModel.clearAll();
}
}

View file

@ -3,15 +3,23 @@ package xl.gui.menu;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JMenuItem;
import xl.model.XLModel;
import xl.gui.StatusLabel;
class ClearMenuItem extends JMenuItem implements ActionListener {
public ClearMenuItem() {
private XLModel xlModel;
private StatusLabel statusLabel;
public ClearMenuItem(XLModel xlModel, StatusLabel statusLabel) {
super("Clear");
this.xlModel = xlModel;
this.statusLabel = statusLabel;
addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
// TODO
xlModel.clearCell();
statusLabel.setText(xlModel.getStatus());
}
}

View file

@ -3,6 +3,7 @@ package xl.gui.menu;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JMenuItem;
import xl.gui.XL;
import xl.gui.XLList;

View file

@ -2,17 +2,22 @@ package xl.gui.menu;
import java.io.FileNotFoundException;
import javax.swing.JFileChooser;
import xl.model.XLModel;
import xl.gui.StatusLabel;
import xl.gui.XL;
class LoadMenuItem extends OpenMenuItem {
public LoadMenuItem(XL xl, StatusLabel statusLabel) {
private XLModel xlModel;
public LoadMenuItem(XL xl, StatusLabel statusLabel, XLModel xlModel) {
super(xl, statusLabel, "Load");
this.xlModel = xlModel;
}
protected void action(String path) throws FileNotFoundException {
// TODO
xlModel.load(path);
}
protected int openDialog(JFileChooser fileChooser) {

View file

@ -3,19 +3,23 @@ package xl.gui.menu;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JMenuItem;
import xl.model.XLModel;
import xl.gui.XL;
class NewMenuItem extends JMenuItem implements ActionListener {
private XL xl;
private XLModel model;
public NewMenuItem(XL xl) {
super("New");
this.xl = xl;
model = new XLModel();
addActionListener(this);
}
public void actionPerformed(ActionEvent event) {
new XL(xl);
new XL(xl, model);
}
}

View file

@ -8,6 +8,7 @@ import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import xl.gui.StatusLabel;
import xl.gui.XL;

View file

@ -2,17 +2,22 @@ package xl.gui.menu;
import java.io.FileNotFoundException;
import javax.swing.JFileChooser;
import xl.model.XLModel;
import xl.gui.StatusLabel;
import xl.gui.XL;
class SaveMenuItem extends OpenMenuItem {
public SaveMenuItem(XL xl, StatusLabel statusLabel) {
private XLModel xlModel;
public SaveMenuItem(XL xl, StatusLabel statusLabel, XLModel xlModel) {
super(xl, statusLabel, "Save");
this.xlModel = xlModel;
}
protected void action(String path) throws FileNotFoundException {
// TODO
xlModel.save(path);
}
protected int openDialog(JFileChooser fileChooser) {

View file

@ -3,6 +3,7 @@ package xl.gui.menu;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JMenuItem;
import xl.gui.XL;
class WindowMenuItem extends JMenuItem implements ActionListener {

View file

@ -2,21 +2,23 @@ package xl.gui.menu;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import xl.model.XLModel;
import xl.gui.StatusLabel;
import xl.gui.XL;
import xl.gui.XLList;
public class XLMenuBar extends JMenuBar {
public XLMenuBar(XL xl, XLList xlList, StatusLabel statusLabel) {
public XLMenuBar(XL xl, XLList xlList, StatusLabel statusLabel, XLModel xlModel) {
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 SaveMenuItem(xl, statusLabel, xlModel));
file.add(new LoadMenuItem(xl, statusLabel, xlModel));
file.add(new NewMenuItem(xl));
file.add(new CloseMenuItem(xl, xlList));
edit.add(new ClearMenuItem());
edit.add(new ClearAllMenuItem());
edit.add(new ClearMenuItem(xlModel, statusLabel));
edit.add(new ClearAllMenuItem(xlModel));
add(file);
add(edit);
add(new WindowMenu(xlList));

View file

@ -0,0 +1,22 @@
package xl.model;
import xl.expr.Environment;
import xl.util.XLException;
/**
* A class representing a cell with a bomb value.
*
* This is essentially a placeholder for an invalid cell reference.
*/
public class BombCell implements Cell {
public BombCell() {}
public double cellValue(Environment e) {
throw XLException.ERROR_RECURSIVE;
}
public String displayValue() {
throw XLException.ERROR_RECURSIVE;
}
}

View file

@ -0,0 +1,11 @@
package xl.model;
import xl.expr.Environment;
/**
* Interface for a cell in the spreadsheet.
*/
public interface Cell {
public double cellValue(Environment e);
public String displayValue();
}

View file

@ -0,0 +1,21 @@
package xl.model;
import xl.expr.Environment;
/**
* A class representing a cell with a comment value.
*/
public class CommentCell implements Cell {
private String commentText;
public CommentCell(String commentText) {
this.commentText = commentText;
}
public double cellValue(Environment e) {
return 0;
}
public String displayValue() {
return commentText;
}
}

View file

@ -0,0 +1,26 @@
package xl.model;
import xl.expr.Environment;
import xl.expr.Expr;
/**
* A class representing a cell with an expression value.
*/
public class ExpressionCell implements Cell {
private Expr expr;
public ExpressionCell(Expr expr) {
this.expr = expr;
}
public double cellValue(Environment env) {
return expr.value(env);
}
public String displayValue() {
return expr.toString();
}
}

View file

@ -0,0 +1,41 @@
package xl.model;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Map;
import xl.expr.Expr;
import xl.expr.ExprParser;
import xl.util.XLException;
/**
* Specialized BufferedReader for reading XL files.
*/
public class XLBufferedReader extends BufferedReader {
public XLBufferedReader(String name) throws FileNotFoundException {
super(new FileReader(name));
}
public void load(Map<String, Cell> map) {
try {
while (ready()) {
String string = readLine();
int i = string.indexOf('=');
String address = string.substring(0, i);
String content = string.substring(i + 1);
if(content.startsWith("#")){
map.put(address, new CommentCell(content.substring(1)));
} else {
ExprParser parser = new ExprParser();
Expr expression = parser.build(content);
map.put(address, new ExpressionCell(expression));
}
}
} catch (Exception e) {
throw new XLException(e.getMessage());
}
}
}

View file

@ -0,0 +1,208 @@
package xl.model;
import java.io.IOException;
import java.util.*;
import xl.expr.Environment;
import xl.expr.ExprParser;
import xl.util.XLException;
public class XLModel extends Observable implements Environment {
private Map<String, Cell> map;
private String status;
private String currentAddress;
// The observers that are registered to be notified
private List<Observer> observers = new ArrayList<>();
public XLModel() {
status = "";
currentAddress = "A1";
map = new java.util.HashMap<String, Cell>();
}
public String getCurrentAddress() {
return currentAddress;
}
public void setCurrentAddress(String currentAddress) {
this.currentAddress = currentAddress;
}
public Optional<Cell> getCell(String address) {
return Optional.ofNullable(map.get(address));
}
public boolean remove(String address) {
// Save the existing cell at the specified address
Cell oldCell = map.get(address);
if (oldCell != null) {
map.remove(address);
// If the output is invalid, restore the old cell
if (hasWrongOutputs()) {
map.put(address, oldCell);
status = "Cannot remove cell";
return false;
}
}
return true;
}
public void add(String address, String text) {
try {
createCell(address, text);
} catch (XLException e) {
status = "Error creating cell";
return;
}
// Tell observers that the model has changed
setChanged();
notifyObservers();
}
public String getStatus() {
return status;
}
public void clearStatus() {
status = "";
}
public void clearCell() {
if (remove(currentAddress)) {
notifyObservers();
}
}
public void clearAll() {
map.clear();
clearStatus();
setChanged();
notifyObservers();
}
public void load(String path) {
try (XLBufferedReader reader = new XLBufferedReader(path)) {
map.clear();
reader.load(map);
status = "";
} catch (IOException e) {
status = "Error loading file";
}
setChanged();
notifyObservers();
}
public void save(String path) {
try (XLPrintStream xlPrintStream = new XLPrintStream(path)) {
xlPrintStream.save(map.entrySet());
} catch (IOException e) {
status = "Error saving file";
}
}
public String getAddressValue(String address) {
if (map.containsKey(address)) {
Cell cell = map.get(address);
if (cell instanceof CommentCell) {
return cell.displayValue();
}
return Double.toString(cell.cellValue(this));
}
return "";
}
public String getAddressContent(String address) {
if (map.containsKey(address)) {
Cell cell = map.get(address);
if (cell instanceof CommentCell) {
return "#" + map.get(address).displayValue();
}
return map.get(address).displayValue();
} else {
return "";
}
}
public boolean containsKey(String address) {
return map.containsKey(address);
}
private void createCell(String address, String text) {
if (text.length() > 0 && text.charAt(0) == '#') {
map.put(address, new CommentCell(text.substring(1)));
} else {
ExprParser parser = new ExprParser();
try {
Cell newCell = new ExpressionCell(parser.build(text));
Cell oldCell = map.get(address);
BombCell bomb = new BombCell();
map.put(address, bomb);
if (hasCircularDependancy(newCell) || hasWrongOutputs()) {
if (oldCell != null) {
map.put(address, oldCell);
} else {
map.remove(address);
}
}
} catch (IOException e) {
throw XLException.ERROR_CREATECELL;
}
}
}
private boolean hasCircularDependancy(Cell cell) {
try {
cell.cellValue(this);
map.put(currentAddress, cell);
status = "";
} catch (XLException e) {
status = e.getMessage();
return true;
} catch (NullPointerException e) {
status = e.toString();
return true;
}
return false;
}
private boolean hasWrongOutputs() {
for (Map.Entry<String, Cell> entry : map.entrySet()) {
try {
entry.getValue().cellValue(this);
} catch (Exception e) {
status = "cannot update";
return true;
}
}
return false;
}
@Override
public double value(String name) {
// Check if the cell is referencing itself
if (currentAddress.equals(name)) {
throw XLException.ERROR_RECURSIVE;
}
// Check if the cell is empty
if (map.containsKey(name)) {
return map.get(name).cellValue(this);
} else {
throw XLException.ERROR_EMPTY_REF;
}
}
public void addObserver(Observer o) {
observers.add(o);
}
public void notifyObservers() {
for (Observer o : observers) {
o.update(this, null);
}
}
}

View file

@ -0,0 +1,31 @@
package xl.model;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.Map.Entry;
import java.util.Set;
/**
* Specialized PrintStream for writing XL files.
*/
public class XLPrintStream extends PrintStream {
public XLPrintStream(String fileName) throws FileNotFoundException {
super(fileName);
}
public void save(Set<Entry<String, Cell>> set) {
for (Entry<String, Cell> entry : set) {
print(entry.getKey());
print('=');
Cell cell = entry.getValue();
if (cell instanceof CommentCell) {
println("#" + cell.displayValue());
} else {
println(cell.displayValue());
}
}
flush();
close();
}
}

View file

@ -1,27 +0,0 @@
package xl.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<String, Object> map) {
try {
while (ready()) {
String string = readLine();
int i = string.indexOf('=');
// TODO
}
} catch (Exception e) {
throw new XLException(e.getMessage());
}
}
}

View file

@ -2,6 +2,10 @@ package xl.util;
public class XLException extends RuntimeException {
public static final XLException ERROR_RECURSIVE = new XLException("Error: Circular Dependency");
public static final XLException ERROR_EMPTY_REF = new XLException("Empty: Reference to empty cell");
public static final XLException ERROR_CREATECELL = new XLException("Error: Could not create cell");
public XLException(String message) {
super(message);
}

View file

@ -1,25 +0,0 @@
package xl.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<Entry<String, Object>> set) {
for (Entry<String, Object> entry : set) {
print(entry.getKey());
print('=');
println(entry.getValue());
}
flush();
close();
}
}