import java.applet.*; import java.awt.*; import java.io.*; import java.lang.*; import java.net.*; public class SpreadSheet extends Applet { String title; Font titleFont; Color cellColor; Color inputColor; int cellWidth = 100; int cellHeight = 15; int titleHeight = 15; int rowLabelWidth = 15; Font inputFont; boolean isStopped = false; boolean fullUpdate = true; int rows; int columns; int currentKey = -1; int selectedRow = -1; int selectedColumn = -1; SpreadSheetInput inputArea; Cell cells[][]; Cell current = null; Frame window = new Frame("Functions Window"); Frame winhelp = new Frame("Help Window"); boolean done = true; Node tempNode; Cell tempCell; Panel top_button_bar; Button cut = new Button("CUT"); Button paste = new Button("PASTE"); Button clearcell = new Button("CLEAR CELL"); Button clearall = new Button("CLEAR ALL"); Panel bottom_button_bar; Button help = new Button("SHOW HELP"); Button nohelp = new Button("CLOSE HELP"); Button quit = new Button("QUIT WINDOW"); TextArea ta = new TextArea("INSTRUCTIONS:\n \nUse the mouse to select the cell in which you want to enter data.\nTHE MOUSE POINTER MUST BE IN THE SPREADSHEET DURING EDITING.\nThe default data type is a VALUE, denoted by a 'v' at the start of the cell.\nFor a FORMULA, use 'f'.\nFor a LABEL, use 'l'.\nAll cells must be in the form of [column][row], where the column is a\ncapital letter and the row is a number.\n \nOPERATORS\nThe operators used are as follows:\n'+' Addition\n'-' Subtraction\n'*' Multiplication\n'/' Division\n'$' Summation (WIP)\n'^' Average (WIP)\n \nFUNCTION BUTTONS\n'CUT' Cut the contents of the current cell.\n'PASTE' Paste the buffer to the current cell.\n'CLEAR CELL' Erase the current cell.\n'CLEAR ALL' Reset the Spreadsheet.\n'SHOW HELP' Show this window.\n'CLOSE HELP' Close this window.\n'QUIT WINDOW' Warning: Closes the function window!\n"); public synchronized void init() { String rs; setLayout(new BorderLayout()); cellColor = Color.white; inputColor = new Color(100, 100, 225); inputFont = new Font("Courier", Font.PLAIN, 10); titleFont = new Font("Courier", Font.BOLD, 12); title = getParameter("title"); if (title == null) { title = "Spreadsheet"; } rs = getParameter("rows"); if (rs == null) { rows = 9; } else { rows = Integer.parseInt(rs); } rs = getParameter("columns"); if (rs == null) { columns = 5; } else { columns = Integer.parseInt(rs); } cells = new Cell[rows][columns]; char l[] = new char[1]; for (int i=0; i < rows; i++) { for (int j=0; j < columns; j++) { cells[i][j] = new Cell(this, Color.lightGray, Color.black, cellColor, cellWidth - 2, cellHeight - 2); l[0] = (char)((int)'a' + j); rs = getParameter("" + new String(l) + (i+1)); if (rs != null) { cells[i][j].setUnparsedValue(rs); } } } Dimension d = size(); inputArea = new SpreadSheetInput(null, this, d.width-2, cellHeight-1, inputColor, Color.white); resize(columns * cellWidth + rowLabelWidth, ((rows + 1) * cellHeight) + cellHeight + titleHeight); this.add(window); Panel bottom_button_bar = new Panel(); Panel top_button_bar = new Panel(); top_button_bar.setLayout(new FlowLayout()); top_button_bar.add(cut); top_button_bar.add(paste); top_button_bar.add(clearcell); top_button_bar.add(clearall); window.add("North", top_button_bar); bottom_button_bar.setLayout(new FlowLayout()); bottom_button_bar.add(help); bottom_button_bar.add(nohelp); bottom_button_bar.add(quit); window.add("South", bottom_button_bar); window.resize(400,150); window.show(); winhelp.resize(500,215); winhelp.add("North",ta); ta.setEditable(false); } // end init public boolean action(Event evt, Object arg){ if (evt.target instanceof Button) { String label = (String)evt.arg; if (label.equals("CLEAR CELL")){ current.setValue(0); } if (label.equals("CLEAR ALL")){ this.init(); repaint(); } if (label.equals("QUIT WINDOW")){ window.hide(); } if (label.equals("SHOW HELP")){ winhelp.show(); } if (label.equals("CLOSE HELP")){ winhelp.hide(); } if (label.equals("CUT")){ tempCell = current; current.setValue(0); repaint(); } if (label.equals("PASTE")){ current.setValue(tempCell.value); current.parseRoot = null; current.parseRoot = tempNode; repaint(); } } return true; } // end action public void setCurrentValue(float val) { if (selectedRow == -1 || selectedColumn == -1) { return; } cells[selectedRow][selectedColumn].setValue(val); repaint(); } // end setCurrentValue public void stop() { isStopped = true; window.hide(); winhelp.hide(); } // end stop public void start() { isStopped = false; } // end start public void destroy() { for (int i=0; i < rows; i++) { for (int j=0; j < columns; j++) { if (cells[i][j].type == Cell.URL) { cells[i][j].updaterThread.stop(); } } } } //end destroy public void setCurrentValue(int type, String val) { if (selectedRow == -1 || selectedColumn == -1) { return; } cells[selectedRow][selectedColumn].setValue(type, val); repaint(); } // end setCurrentValue public void update(Graphics g) { if (! fullUpdate) { int cx, cy; g.setFont(titleFont); for (int i=0; i < rows; i++) { for (int j=0; j < columns; j++) { if (cells[i][j].needRedisplay) { cx = (j * cellWidth) + 2 + rowLabelWidth; cy = ((i+1) * cellHeight) + 2 + titleHeight; cells[i][j].paint(g, cx, cy); } } } } else { paint(g); fullUpdate = false; } } // end update public void recalculate() { int i,j; for (i=0; i < rows; i++) { for (j=0; j < columns; j++) { if (cells[i][j] != null && cells[i][j].type == Cell.FORMULA) { cells[i][j].setRawValue(evaluateFormula(cells[i][j].parseRoot)); cells[i][j].needRedisplay = true; } } } repaint(); } // recalculate public float evaluateFormula(Node n) { float val = 0.0f; int i; Node endNode; float beg; float end; int begrow; int endrow; int temp; //n.print(3); if (n == null) { return val; } switch (n.type) { case Node.OP: val = evaluateFormula(n.left); switch (n.op) { case '+': val += evaluateFormula(n.right); break; case '*': val *= evaluateFormula(n.right); break; case '-': val -= evaluateFormula(n.right); break; case '/': val /= evaluateFormula(n.right); break; // case '$' WIP case '$': beg = n.left.value; end = evaluateFormula(n.right); begrow = n.left.row; endrow = n.right.row; for (i=begrow; i<=endrow;) val += n.value; break; // case '^' WIP case '^': beg = n.left.value; end = evaluateFormula(n.right); begrow = n.left.row; endrow = n.right.row; for (i=begrow; i<=endrow;) val += n.value; int count = endrow - begrow; val /= count; } break; case Node.VALUE: return n.value; case Node.CELL: if (n == null) { } else { if (cells[n.row][n.column] == null) { } else { return cells[n.row][n.column].value; } } } return val; } // end evaluateFormula public synchronized void paint(Graphics g) { int i, j; int cx, cy; char l[] = new char[1]; Dimension d = size(); g.setFont(titleFont); i = g.getFontMetrics().stringWidth(title); g.drawString((title == null) ? "Spreadsheet" : title, (d.width - i) / 2, 12); g.setColor(inputColor); g.fillRect(0, cellHeight, d.width, cellHeight); g.setFont(titleFont); for (i=0; i < rows+1; i++) { cy = (i+2) * cellHeight; g.setColor(getBackground()); g.draw3DRect(0, cy, d.width, 2, true); if (i < rows) { g.setColor(Color.red); g.drawString("" + (i+1), 2, cy + 12); } } g.setColor(Color.red); for (i=0; i < columns; i++) { cx = i * cellWidth; g.setColor(getBackground()); g.draw3DRect(cx + rowLabelWidth, 2 * cellHeight, 1, d.height, true); if (i < columns) { g.setColor(Color.red); l[0] = (char)((int)'A' + i); g.drawString(new String(l), cx + rowLabelWidth + (cellWidth / 2), d.height - 3); } } for (i=0; i < rows; i++) { for (j=0; j < columns; j++) { cx = (j * cellWidth) + 2 + rowLabelWidth; cy = ((i+1) * cellHeight) + 2 + titleHeight; if (cells[i][j] != null) { cells[i][j].paint(g, cx, cy); } } } g.setColor(getBackground()); g.draw3DRect(0, titleHeight, d.width, d.height - titleHeight, false); inputArea.paint(g, 1, titleHeight + 1); } // end paint public boolean mouseDown(Event evt, int x, int y) { Cell cell; if (y < (titleHeight + cellHeight)) { selectedRow = -1; if (y <= titleHeight && current != null) { current.deselect(); current = null; } return true; } if (x < rowLabelWidth) { selectedRow = -1; if (current != null) { current.deselect(); current = null; } return true; } selectedRow = ((y - cellHeight - titleHeight) / cellHeight); selectedColumn = (x - rowLabelWidth) / cellWidth; if (selectedRow > rows || selectedColumn >= columns) { selectedRow = -1; if (current != null) { current.deselect(); current = null; } } else { if (selectedRow >= rows) { selectedRow = -1; if (current != null) { current.deselect(); current = null; } return true; } cell = cells[selectedRow][selectedColumn]; inputArea.setText(new String(cell.getPrintString())); if (current != null) { current.deselect(); } current = cell; current.select(); requestFocus(); fullUpdate = true; repaint(); } return true; } // end mouseDown public boolean keyDown(Event evt, int key) { fullUpdate=true; inputArea.keyDown(key); return true; } // end keyDown } // end class SpreadSheet class CellUpdater extends Thread { Cell target; InputStream dataStream = null; StreamTokenizer tokenStream; public CellUpdater(Cell c) { super("cell updater"); target = c; } // end CellUpdater public void run() { try { dataStream = new URL(target.app.getDocumentBase(), target.getValueString()).openStream(); tokenStream = new StreamTokenizer(dataStream); tokenStream.eolIsSignificant(false); while (true) { switch (tokenStream.nextToken()) { case tokenStream.TT_EOF: dataStream.close(); return; default: break; case tokenStream.TT_NUMBER: target.setTransientValue((float)tokenStream.nval); if (! target.app.isStopped && ! target.paused) { target.app.repaint(); } break; } try { Thread.sleep(2000); } catch (InterruptedException e) { break; } } } catch (IOException e) { return; } } } // end class CellUpdater class Cell { public static final int VALUE = 0; public static final int LABEL = 1; public static final int URL = 2; public static final int FORMULA = 3; Node parseRoot; boolean needRedisplay; boolean selected = false; boolean transientValue = false; public int type = Cell.VALUE; String valueString = ""; String printString = "v"; float value; Color bgColor; Color fgColor; Color highlightColor; int width; int height; SpreadSheet app; CellUpdater updaterThread; boolean paused = false; public Cell(SpreadSheet app, Color bgColor, Color fgColor, Color highlightColor, int width, int height) { this.app = app; this.bgColor = bgColor; this.fgColor = fgColor; this.highlightColor = highlightColor; this.width = width; this.height = height; needRedisplay = true; } // end Cell public void setRawValue(float f) { valueString = Float.toString(f); value = f; } // end setRawValue public void setValue(float f) { setRawValue(f); printString = "v" + valueString; type = Cell.VALUE; paused = false; app.recalculate(); needRedisplay = true; } // end set Value public void setTransientValue(float f) { transientValue = true; value = f; needRedisplay = true; app.recalculate(); } // end setTransientValue public void setUnparsedValue(String s) { switch (s.charAt(0)) { case 'v': setValue(Cell.VALUE, s.substring(1)); break; case 'f': setValue(Cell.FORMULA, s.substring(1)); break; case 'l': setValue(Cell.LABEL, s.substring(1)); break; case 'u': setValue(Cell.URL, s.substring(1)); break; } } // end setUnparsedValue /** * Parse a spreadsheet formula. The syntax is defined as: * * formula -> value * formula -> value op value * value -> '(' formula ')' * value -> cell * value -> * op -> '+' | '*' | '/' | '-' * cell -> */ public String parseFormula(String formula, Node node) { String subformula; String restFormula; float value; int length = formula.length(); Node left; Node right; char op; if (formula == null) { return null; } subformula = parseValue(formula, node); if (subformula == null || subformula.length() == 0) { return null; } if (subformula == formula) { return formula; } // parse an operator and then another value switch (op = subformula.charAt(0)) { case 0: return null; case ')': return subformula; case '+': case '*': case '-': case '/': restFormula = subformula.substring(1); subformula = parseValue(restFormula, right=new Node()); if (subformula != restFormula) { left = new Node(node); node.left = left; node.right = right; node.op = op; node.type = Node.OP; return subformula; } else { return formula; } default: return formula; } } // end parseFormula public String parseValue(String formula, Node node) { char c = formula.charAt(0); String subformula; String restFormula; float value; int row; int column; restFormula = formula; if (c == '(') { restFormula = formula.substring(1); subformula = parseFormula(restFormula, node); if (subformula == null || subformula.length() == restFormula.length()) { return formula; } else if (! (subformula.charAt(0) == ')')) { return formula; } restFormula = subformula; } else if (c >= '0' && c <= '9') { int i; try { value = Float.valueOf(formula).floatValue(); } catch (NumberFormatException e) { return formula; } for (i=0; i < formula.length(); i++) { c = formula.charAt(i); if ((c < '0' || c > '9') && c != '.') { break; } } node.type = Node.VALUE; node.value = value; restFormula = formula.substring(i); return restFormula; } else if (c >= 'A' && c <= 'Z') { int i; column = c - 'A'; restFormula = formula.substring(1); row = Float.valueOf(restFormula).intValue(); for (i=0; i < restFormula.length(); i++) { c = restFormula.charAt(i); if (c < '0' || c > '9') { break; } } node.row = row - 1; node.column = column; node.type = Node.CELL; if (i == restFormula.length()) { restFormula = null; } else { restFormula = restFormula.substring(i); if (restFormula.charAt(0) == 0) { return null; } } } return restFormula; } // end parseValue public void setValue(int type, String s) { paused = false; if (this.type == Cell.URL) { updaterThread.stop(); updaterThread = null; } valueString = new String(s); this.type = type; needRedisplay = true; switch (type) { case Cell.VALUE: setValue(Float.valueOf(s).floatValue()); break; case Cell.LABEL: printString = "l" + valueString; break; case Cell.URL: printString = "u" + valueString; updaterThread = new CellUpdater(this); updaterThread.start(); break; case Cell.FORMULA: parseFormula(valueString, parseRoot = new Node()); printString = "f" + valueString; break; } app.recalculate(); } // end setValue public String getValueString() { return valueString; } // end getValueString public String getPrintString() { return printString; } // end getPrintString public void select() { selected = true; paused = true; } // end select public void deselect() { selected = false; paused = false; needRedisplay = true; app.repaint(); } // end deselect public void paint(Graphics g, int x, int y) { if (selected) { g.setColor(highlightColor); } else { g.setColor(bgColor); } g.fillRect(x, y, width - 1, height); if (valueString != null) { switch (type) { case Cell.VALUE: case Cell.LABEL: g.setColor(fgColor); break; case Cell.FORMULA: g.setColor(Color.red); break; case Cell.URL: g.setColor(Color.blue); break; } if (transientValue){ g.drawString("" + value, x, y + (height / 2) + 5); } else { if (valueString.length() > 14) { g.drawString(valueString.substring(0, 14), x, y + (height / 2) + 5); } else { g.drawString(valueString, x, y + (height / 2) + 5); } } } needRedisplay = false; } // end paint } // end class Cell class Node { public static final int OP = 0; public static final int VALUE = 1; public static final int CELL = 2; int type; Node left; Node right; int row; int column; float value; char op; public Node() { left = null; right = null; value = 0; row = -1; column = -1; op = 0; type = Node.VALUE; } // Node public Node(Node n) { left = n.left; right = n.right; value = n.value; row = n.row; column = n.column; op = n.op; type = n.type; } // end Node public void indent(int ind) { for (int i = 0; i < ind; i++) { System.out.print(" "); } } // end indent public void print(int indentLevel) { char l[] = new char[1]; indent(indentLevel); System.out.println("NODE type=" + type); indent(indentLevel); switch (type) { case Node.VALUE: System.out.println(" value=" + value); break; case Node.CELL: l[0] = (char)((int)'A' + column); System.out.println(" cell=" + new String(l) + (row+1)); break; case Node.OP: System.out.println(" op=" + op); left.print(indentLevel + 3); right.print(indentLevel + 3); break; } } // end print } // end class Node class InputField { int maxchars = 50; int cursorPos = 0; Applet app; String sval; char buffer[]; int nChars; int width; int height; Color bgColor; Color fgColor; public InputField(String initValue, Applet app, int width, int height, Color bgColor, Color fgColor) { this.width = width; this.height = height; this.bgColor = bgColor; this.fgColor = fgColor; this.app = app; buffer = new char[maxchars]; nChars = 0; if (initValue != null) { initValue.getChars(0, initValue.length(), this.buffer, 0); nChars = initValue.length(); } sval = initValue; } // end InputField public void setText(String val) { int i; for (i=0; i < maxchars; i++) { buffer[i] = 0; } sval = new String(val); if (val == null) { sval = ""; nChars = 0; buffer[0] = 0; } else { sval.getChars(0, sval.length(), buffer, 0); nChars = val.length(); sval = new String(buffer); } } // end setText public String getValue() { return sval; } // end getValue public void paint(Graphics g, int x, int y) { g.setColor(bgColor); g.fillRect(x, y, width, height); if (sval != null) { g.setColor(fgColor); g.drawString(sval, x, y + (height / 2) + 3); } } // end paint public void mouseUp(int x, int y) { // set the edit position } // end mouseUp public void keyDown(int key) { if (nChars < maxchars) { switch (key) { case 8: // delete --nChars; if (nChars < 0) { nChars = 0; } buffer[nChars] = 0; sval = new String(new String(buffer)); break; case 10: // return selected(); break; default: buffer[nChars++] = (char)key; sval = new String(new String(buffer)); break; } } app.repaint(); } // end keyDown public void selected() { } // end selected } // end class InputField class SpreadSheetInput extends InputField { public SpreadSheetInput(String initValue, SpreadSheet app, int width, int height, Color bgColor, Color fgColor) { super(initValue, app, width, height, bgColor, fgColor); } // end SpreadSheetInput public void selected() { float f; switch (sval.charAt(0)) { case 'v': try { f = Float.valueOf(sval.substring(1)).floatValue(); ((SpreadSheet)app).setCurrentValue(f); } catch (NumberFormatException e) { System.out.println("Not a float..."); } break; case 'l': ((SpreadSheet)app).setCurrentValue(Cell.LABEL, sval.substring(1)); break; case 'u': ((SpreadSheet)app).setCurrentValue(Cell.URL, sval.substring(1)); break; case 'f': ((SpreadSheet)app).setCurrentValue(Cell.FORMULA, sval.substring(1)); break; } } // end selected } // end class SpreadSheetInput