0001: /*************************************************************************
0002: * *
0003: * 1) This source code file, in unmodified form, and compiled classes *
0004: * derived from it can be used and distributed without restriction, *
0005: * including for commercial use. (Attribution is not required *
0006: * but is appreciated.) *
0007: * *
0008: * 2) Modified versions of this file can be made and distributed *
0009: * provided: the modified versions are put into a Java package *
0010: * different from the original package, edu.hws; modified *
0011: * versions are distributed under the same terms as the original; *
0012: * and the modifications are documented in comments. (Modification *
0013: * here does not include simply making subclasses that belong to *
0014: * a package other than edu.hws, which can be done without any *
0015: * restriction.) *
0016: * *
0017: * David J. Eck *
0018: * Department of Mathematics and Computer Science *
0019: * Hobart and William Smith Colleges *
0020: * Geneva, New York 14456, USA *
0021: * Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
0022: * *
0023: *************************************************************************/package edu.hws.jcm.awt;
0024:
0025: import edu.hws.jcm.data.*;
0026: import java.awt.*;
0027: import java.awt.event.*;
0028: import java.util.Vector;
0029: import java.io.*;
0030:
0031: /**
0032: * A DataTableInput lets the user input a grid of real numbers arranged
0033: * in rows and columns. Each column has a name, and rows are numberd
0034: * starting from 1. The column names and row numbers can be
0035: * displayed, optionally. By default, a new row is added automatically
0036: * if the user moves down out of the last row by pressing return
0037: * or down-arrow, provided the last row is non-empty. Rows can also be
0038: * added programmatically. Columns are usually added in the constructor,
0039: * but they can also be added later. If the user leaves a cell
0040: * at a time when the content of that cell does not represent a legal
0041: * number, then the message "bad input" will be displayed in the cell.
0042: * <p>A DataTableInput can be given a name and can then be added to a parser.
0043: * The parser will then recognize the name of the table. In an expression, the table name
0044: * can be followed by a ".", then one of the column names from table, then
0045: * an expression in parentheses. This represents a reference to the number in
0046: * one of the cells in the table. The expression gives the number of
0047: * the row, where rows are numbered starting from 1. For example:
0048: * "data.A(3)" represents the third number in column "A" in a table
0049: * named data. The table name and "." can also be followed by the word "sum" and
0050: * an expression in parentheses. An expression used in this way
0051: * can include references to the column names in the table and to
0052: * the special value "rowNumber". The value of expression is computed
0053: * for each row in the table and the sum of the values is computed. In the expression that
0054: * is being summed, a column name represents the
0055: * number in that column and rowNumber represents the number of
0056: * the row. For example: "data.sum(A^2)". Finally, the "." can be
0057: * followed by the word "count". "count" can, optionally be followed
0058: * by an empty pair of parentheses. This represents the number of
0059: * rows. For example: "data.count". Empty rows at the bottom
0060: * of the table are ignored in both "sum" and "count".
0061: * <p>Note that rows are numbered starting from 1 because row numbers
0062: * can be visible to the user, who shouldn't be subjected to zero-based
0063: * numbering. However, columns are always numbered from zero.
0064: */
0065: public class DataTableInput extends Panel implements ParserExtension {
0066:
0067: private String objectName; // The name of the DataTableObject
0068:
0069: private Vector rows; // Vector of double[]. Each entry holds
0070: // the values in a row of a table. An
0071: // entry is null for an empty row.
0072:
0073: private Vector rowStrings; // Row values as Strings. The string "bad input"
0074: // means that the string in the cell does not
0075: // represent a legal number
0076:
0077: private String[] columnName; // The name to be used to refer to the column
0078: // in expressions. Also used as a label at the
0079: // top of the column.
0080:
0081: private int columnCount; // Number of columns
0082:
0083: private int currentRow = 1; // The number of the "currentRow" which is
0084: // specified in the setCurrentRowNumber() method.
0085:
0086: private double emptyCellValue = Double.NaN; // An empty cell is considered to have this value.
0087:
0088: private boolean throwErrors = false; // Should an error be thrown when getValue(col,row)
0089: // sees "bad input" in the cell. If not, then the
0090: // value is considered to be Double.NaN.
0091:
0092: private boolean autoAddRows = true; // If this is true, an empty row is added
0093: // when the user presses enter or hits
0094: // down-arrow while in the last row, if that row
0095: // is empty.
0096:
0097: private boolean showColumnTitles; // If true, column names are shown above the columns
0098:
0099: private boolean showRowNumbers; // If true, row numbers are shown to left of each row
0100:
0101: private DisplayPanel canvas; // Where the table appears; nested class DisplayPanel is defined below.
0102:
0103: private long serialNumber; // This is incremented each time the table changes.
0104:
0105: private Color labelBackground = new Color(220, 220, 220); // Colors of various things.
0106: private Color cellBackground = new Color(255, 255, 220);
0107: private Color blankBackground = Color.gray;
0108: private Color gridColor = Color.blue;
0109:
0110: /**
0111: * Create a DataTableInput with no columns. Columns can be added later
0112: * using the addColumn() methods. The table initially has no name.
0113: */
0114: public DataTableInput() {
0115: this (null, 0);
0116: }
0117:
0118: /**
0119: * Create a table with the specified column names. If columnNames
0120: * is null, the number of columns is zero. The name can be null, if you
0121: * don't need a name for the table. The length of the array determines
0122: * the number of columns in the table.
0123: */
0124: public DataTableInput(String name, String[] columnNames) {
0125: this (name, columnNames == null ? 0 : columnNames.length);
0126: if (columnNames != null)
0127: for (int i = 0; i < columnNames.length; i++)
0128: setColumnName(i, columnNames[i]);
0129: }
0130:
0131: /**
0132: * Create a table with the specified number of columns,
0133: * named "A", "B", etc. The name can be null, if you
0134: * don't need a name for the table. The number of columns can be zero.
0135: */
0136: public DataTableInput(String name, int columns) {
0137: if (columns < 0)
0138: columns = 0;
0139: setName(name);
0140: rowStrings = new Vector();
0141: rows = new Vector();
0142: rowStrings.addElement(null);
0143: rows.addElement(null);
0144: columnName = new String[columns];
0145: for (int i = 0; i < columns; i++)
0146: columnName[i] = "" + (char) ((int) 'A' + i);
0147: canvas = new DisplayPanel();
0148: setLayout(new BorderLayout());
0149: setBackground(Color.lightGray);
0150: add(canvas, BorderLayout.CENTER);
0151: add(canvas.vScroll, BorderLayout.EAST);
0152: columnCount = columns;
0153: }
0154:
0155: //------------------------- Data access and variables
0156:
0157: /**
0158: * Required by the ParserExtension interface and not meant to be called directly.
0159: * This is called by a parser if it encounters the name of the table in an
0160: * expression. It parses the complete table reference, such as "data.A(3)"
0161: * or "data.sum(A^2)".
0162: */
0163: public void doParse(Parser parser, ParserContext context) {
0164: int tok = context.next();
0165: if (tok != ParserContext.OPCHARS
0166: || !context.tokenString.equals("."))
0167: throw new ParseError(
0168: "Expected a '.' after the name of a data table.",
0169: context);
0170: tok = context.next();
0171: if (tok != ParserContext.IDENTIFIER)
0172: throw new ParseError(
0173: "Expected 'sum', 'count', or the name of a column after data table name.",
0174: context);
0175: String commandName = context.tokenString;
0176: int command = -10;
0177: for (int i = 0; i < columnCount; i++)
0178: if (commandName.equalsIgnoreCase(getColumnName(i))) {
0179: command = i;
0180: break;
0181: }
0182: if (command == -10) {
0183: if (commandName.equalsIgnoreCase("sum"))
0184: command = -1;
0185: else if (commandName.equalsIgnoreCase("count"))
0186: command = -2;
0187: }
0188: if (command == -10)
0189: throw new ParseError("Unrecognized table command \""
0190: + commandName + "\".", context);
0191: if (command == -2) {
0192: if (context.look() == ParserContext.OPCHARS
0193: && context.tokenString.equals("(")) {
0194: context.next();
0195: if (context.next() != ParserContext.OPCHARS
0196: || !context.tokenString.equals(")"))
0197: throw new ParseError(
0198: "Missing right parenthesis; \"count\" does not take a parameter.",
0199: context);
0200: }
0201: context.prog.addCommandObject(new DTEC(-2, null));
0202: return;
0203: }
0204: if (context.next() != ParserContext.OPCHARS
0205: || !context.tokenString.equals("("))
0206: throw new ParseError(
0207: "Expected a left parentheses after table command \""
0208: + commandName + "\".", context);
0209: ExpressionProgram saveProg = context.prog;
0210: ExpressionProgram tableProg = new ExpressionProgram();
0211: context.prog = tableProg;
0212: if (command == -1) {
0213: context.mark();
0214: for (int i = 0; i < columnCount; i++)
0215: context.add(getColumnVariable(i));
0216: context.add(getRowNumberVariable());
0217: }
0218: parser.parseExpression(context);
0219: context.prog = saveProg;
0220: if (context.next() != ParserContext.OPCHARS
0221: || !context.tokenString.equals(")"))
0222: throw new ParseError("Missing right parenthesis.", context);
0223: context.prog.addCommandObject(new DTEC(command, tableProg));
0224: if (command == -1)
0225: context.revert();
0226: }
0227:
0228: /**
0229: * Return the number of rows in the table, ignoring empty rows at the bottom
0230: * of the table. Note that an empty row that precedes some non-empty row
0231: * is included in the count.
0232: */
0233: public int getNonEmptyRowCount() {
0234: int rowCt = rows.size();
0235: while (rowCt > 0 && rows.elementAt(rowCt - 1) == null)
0236: rowCt--;
0237: return rowCt;
0238: }
0239:
0240: /**
0241: * Get the number in the specified row and column. Rows are numberd starting
0242: * from 1, but columns are numbered starting from zero. If the specified
0243: * cell does not exist in the table, Double.NaN is returned. If the cell is empty,
0244: * emptyCellValue is returned. If the content of the cell does not define
0245: * a legal real number, then the action depends on the value of the missingValueIsError
0246: * property: If this property is true, then a JCMError is thrown; if it is
0247: * false, then Double.NaN is returned.
0248: */
0249: public double getCellContents(int row, int col) {
0250: if (row < 1 || row > rows.size() || col < 0
0251: || col > columnCount)
0252: return Double.NaN;
0253: return canvas.getValue(col, row);
0254: }
0255:
0256: /**
0257: * Put the given real number, val, in the cell in the specified row
0258: * and column, where rows are numbered starting from 1 and columns are
0259: * numbered starting from zero. This is ignored if the specified row
0260: * and column do not exist in the table.
0261: */
0262: public void setCellContents(int row, int col, double val) {
0263: if (row < 1 || row > rows.size() || col < 0
0264: || col > columnCount)
0265: return;
0266: canvas.setValue(col, row, val);
0267: }
0268:
0269: /**
0270: * Set the current row in the table. If the parameter is less than 1, the current
0271: * row is set to 1.
0272: * (The table keeps track of a "current row number", which is always greater than
0273: * or equal to 1. This row number is used when a column variable or row number
0274: * variable is evaluated. These variables can be added to a parser using the
0275: * addVariablesToParser method, and can then be used in expressions parsed by
0276: * that parser. When the row number variable, which is named rowNumber, is
0277: * evaluated, its value is the current row number in the table. When a column
0278: * variable is evaluated, its value is the number in the cell in the associated
0279: * column and in the current row. The setCurrentRowNumber() method, in combination
0280: * with the getNonEmptyRowCount() method allow you to iterate through the rows
0281: * of the table and evaluate the expression for each row.)
0282: */
0283: public void setCurrentRowNumber(int i) {
0284: currentRow = (i < 1) ? 1 : i;
0285: }
0286:
0287: /**
0288: * Return the current row number.
0289: */
0290: public int getCurrentRowNumber() {
0291: return currentRow;
0292: }
0293:
0294: /**
0295: * Return a column variable for the specified column, where columns are
0296: * numbered starting from 1. The value of this variable is the number
0297: * in the specified column and in the current row of the table (as set
0298: * by the setCurrentRowNumber() method.) The name of the variable is
0299: * the name of the column. This method is protected since variables are
0300: * not meant to be used as regular variables. But they can be added to
0301: * a Parser by calling the addVariablesToParser() method.)
0302: */
0303: protected Variable getColumnVariable(final int columnNum) {
0304: if (columnNum < 0 || columnNum >= columnCount)
0305: throw new IllegalArgumentException(
0306: "Column number out of range.");
0307: return new Variable(getColumnName(columnNum), 0) {
0308: public void setVal(double v) {
0309: if (currentRow < rows.size())
0310: canvas.setValue(columnNum, currentRow, v);
0311: super .setVal(v);
0312: }
0313:
0314: public double getVal() {
0315: if (currentRow > rows.size())
0316: return Double.NaN;
0317: else
0318: return canvas.getValue(columnNum, currentRow);
0319: }
0320: };
0321: }
0322:
0323: /**
0324: * Get a variable that represents the current row number in the table,
0325: * as set by the setCurrentRowNumber() method. The name of the
0326: * variable is rowNumber.
0327: */
0328: protected Variable getRowNumberVariable() {
0329: return new Variable("rowNumber", 0) {
0330: public void setVal(double v) {
0331: int val = (int) (v + 0.5);
0332: if (val < 1 || val > getNonEmptyRowCount())
0333: val = getNonEmptyRowCount() + 1;
0334: currentRow = val;
0335: super .setVal(val);
0336: }
0337:
0338: public double getVal() {
0339: return currentRow;
0340: }
0341: };
0342: }
0343:
0344: /**
0345: * Add a row number variable (from the getRowNumberVariable() method) and
0346: * a column variable for each column (from the getColumnVariable() method)
0347: * to the parser. The parser will then be able to parse expressions that
0348: * refer to these variables. The value of such an expression depends on
0349: * the current row number, as set by setCurrentRowNumber().
0350: */
0351: public void addVariablesToParser(Parser p) {
0352: p.add(getRowNumberVariable());
0353: for (int i = 0; i < columnCount; i++) {
0354: p.add(getColumnVariable(i));
0355: }
0356: }
0357:
0358: /**
0359: * Get the serial number of the table. This is incremented each time the
0360: * table changes in any way.
0361: */
0362: public long getSerialNumber() {
0363: return serialNumber;
0364: }
0365:
0366: //-------------------------
0367:
0368: /**
0369: * Set the throwErrors property. If this is true, then a JCMError is thrown when
0370: * an attempt is made to use the value of a cell that contains an invalid String.
0371: * Note that referring to an empty cell is not an error. The default value is true.
0372: */
0373: public void setThrowErrors(boolean throwErr) {
0374: throwErrors = throwErr;
0375: }
0376:
0377: /**
0378: * Get the value of the throwErrors property, which determines whether an error
0379: * is thrown when an attempt is made to refer to the value of a cell that
0380: * contains an invalid string.
0381: */
0382: public boolean getThrowErrors() {
0383: return throwErrors;
0384: }
0385:
0386: /**
0387: * Set the value that should be returned when the value of an empty cell is
0388: * requested. The default value is Double.NaN. Another plausible value, in
0389: * some circumstances, would be zero.
0390: */
0391: public void setEmptyCellValue(double val) {
0392: emptyCellValue = val;
0393: }
0394:
0395: /**
0396: * Get the value that is represented by an empty cell.
0397: */
0398: public double getEmptyCellValue() {
0399: return emptyCellValue;
0400: }
0401:
0402: /**
0403: * If the value of autoAddRows is true, then an empty row is added to the table
0404: * automatically when the user attempts to move down from the last row of
0405: * the table, provided that the last row is non-empty (so there can only be
0406: * one auto-added row at a time). If the user leaves this row while it is
0407: * still empty, it will automatically be deleted. The default value is true.
0408: */
0409: public void setAutoAddRows(boolean auto) {
0410: // Set the autoAddRows property.
0411: autoAddRows = auto;
0412: canvas.lastRowAutoAdded = false;
0413: }
0414:
0415: /**
0416: * Get the value of the autoAddRows property, which determines whether empty
0417: * rows are automatically added to the bottom of the table when needed.
0418: */
0419: public boolean getAutoAddRows() {
0420: return autoAddRows;
0421: }
0422:
0423: /**
0424: * Set the name of this DataTableInput. This is only needed if the table is
0425: * to be added to a parser. The name should be a legal identifier.
0426: */
0427: public void setName(String name) {
0428: objectName = name;
0429: }
0430:
0431: /**
0432: * Get the name of the DataInputTable (which might be null).
0433: */
0434: public String getName() {
0435: return objectName;
0436: }
0437:
0438: /**
0439: * Set the name of column number i, where columns are numbered starting
0440: * from zero. If column variables are to be used or if
0441: * the DataTableInput itself is to be added to a parser, then the name should
0442: * be a legal identifier. If the showColumnTitles property is set to true,
0443: * then column names are shown at the top of the table.
0444: */
0445: public void setColumnName(int i, String name) {
0446: if (name != null)
0447: columnName[i] = name;
0448: }
0449:
0450: /**
0451: * Get the name of column number i, where columns are numbered starting from zero.
0452: */
0453: public String getColumnName(int i) {
0454: return columnName[i];
0455: }
0456:
0457: /**
0458: * Add the specified number of empty rows at the bottom of the table. If you
0459: * want a table with a fixed number of rows, add them with this method and
0460: * set the autoAddRows property to false.
0461: */
0462: public void addRows(int rowCt) {
0463: canvas.addRows(rowCt, rows.size());
0464: }
0465:
0466: /**
0467: * Insert a row before the row that contains the cell that the user is editing.
0468: */
0469: public void insertRow() {
0470: canvas.addRows(1, canvas.activeRow);
0471: }
0472:
0473: /**
0474: * Delete the row that contains the cell that the user is editing. However,
0475: * if that is the only row in the table, just make the row empty.
0476: */
0477: public void deleteCurrentRow() {
0478: if (canvas.activeRow == rows.size() - 1 && rows.size() > 1) {
0479: canvas.setActive(canvas.activeRow - 1, canvas.activeColumn);
0480: rows.removeElementAt(canvas.activeRow + 1);
0481: rowStrings.removeElementAt(canvas.activeRow + 1);
0482: } else {
0483: rows.removeElementAt(canvas.activeRow);
0484: rowStrings.removeElementAt(canvas.activeRow);
0485: }
0486: if (rows.size() == 0) {
0487: rows.addElement(null);
0488: rowStrings.addElement(null);
0489: }
0490: String[] vals = (String[]) rowStrings
0491: .elementAt(canvas.activeRow);
0492: if (vals == null || vals[canvas.activeColumn] == null)
0493: canvas.input.setText("");
0494: else
0495: canvas.input.setText(vals[canvas.activeColumn]);
0496: canvas.checkScroll();
0497: canvas.repaint();
0498: if (canvas.rowLabelCanvas != null)
0499: canvas.rowLabelCanvas.repaint();
0500: if (canvas.columnLabelCanvas != null)
0501: canvas.columnLabelCanvas.repaint();
0502: serialNumber++;
0503: }
0504:
0505: /**
0506: * Remove all rows from the table, leaving just one empty row.
0507: */
0508: public void clear() {
0509: rows = new Vector();
0510: rowStrings = new Vector();
0511: rows.addElement(null);
0512: rowStrings.addElement(null);
0513: canvas.setActive(0, 0);
0514: canvas.checkScroll();
0515: canvas.repaint();
0516: if (canvas.rowLabelCanvas != null)
0517: canvas.rowLabelCanvas.repaint();
0518: if (canvas.columnLabelCanvas != null)
0519: canvas.columnLabelCanvas.repaint();
0520: serialNumber++;
0521: }
0522:
0523: /**
0524: * Get the number of columns in the table.
0525: */
0526: public int getColumnCount() {
0527: return columnName.length;
0528: }
0529:
0530: /**
0531: * Add a column at the right side of the table, with all cells initially
0532: * empty. The name of the columns will be single letter such as 'A', 'B', ...
0533: */
0534: public int addColumn() {
0535: return addColumn(null);
0536: }
0537:
0538: /**
0539: * Add a column with the specified name at the right side of the table, with all cells initially
0540: * empty. This is inefficient if the table already contains a bunch of non-empty rows.
0541: */
0542: public int addColumn(String name) {
0543: int newSize = columnName.length + 1;
0544: String[] newNames = new String[newSize];
0545: for (int i = 0; i < columnName.length; i++)
0546: newNames[i] = columnName[i];
0547: if (name == null)
0548: newNames[newSize - 1] = ""
0549: + (char) ((int) 'A' + newSize - 1);
0550: else
0551: newNames[newSize - 1] = name;
0552: columnName = newNames;
0553: int rowCt = rows.size();
0554: for (int i = 0; i < rowCt; i++) {
0555: if (rows.elementAt(i) != null) {
0556: double[] oldRow = (double[]) rows.elementAt(i);
0557: double[] newRow = new double[newSize];
0558: for (int j = 0; j < oldRow.length; j++)
0559: newRow[j] = oldRow[j];
0560: newRow[newSize - 1] = Double.NaN;
0561: rows.setElementAt(newRow, i);
0562: }
0563: if (rowStrings.elementAt(i) != null) {
0564: String[] oldRow = (String[]) rows.elementAt(i);
0565: String[] newRow = new String[newSize];
0566: for (int j = 0; j < oldRow.length; j++)
0567: newRow[j] = oldRow[j];
0568: rowStrings.setElementAt(newRow, i);
0569: }
0570: }
0571: if (canvas.hScroll != null)
0572: canvas.checkScroll();
0573: canvas.repaint();
0574: if (canvas.columnLabelCanvas != null)
0575: canvas.columnLabelCanvas.repaint();
0576: columnCount = columnName.length;
0577: serialNumber++;
0578: return columnCount - 1;
0579: }
0580:
0581: /**
0582: * Test whether the column name is shown at the top of each column.
0583: */
0584: public boolean getShowColumnTitles() {
0585: return showColumnTitles;
0586: }
0587:
0588: /**
0589: * If set to true, then the column name is shown at the top of each column. The
0590: * default value is false. This is meant to be called before the table has been
0591: * shown on the screen, such as in the init() method of an applet. If you call it
0592: * after the table has already been shown, you will have to validate the panel
0593: * yourself.
0594: */
0595: public void setShowColumnTitles(boolean show) {
0596: if (show == showColumnTitles)
0597: return;
0598: showColumnTitles = show;
0599: if (showColumnTitles) {
0600: canvas.makeColumnLabelCanvas();
0601: add(canvas.columnLabelCanvas, BorderLayout.NORTH);
0602: } else {
0603: remove(canvas.columnLabelCanvas);
0604: canvas.columnLabelCanvas = null;
0605: }
0606: }
0607:
0608: /**
0609: * Test whether row numbers are shown.
0610: */
0611: public boolean getShowRowNumbers() {
0612: return showRowNumbers;
0613: }
0614:
0615: /**
0616: * If set to true, then the row number is shown at the left of each row. The
0617: * default value is false. This is meant to be called before the table has been
0618: * shown on the screen, such as in the init() method of an applet. If you call it
0619: * after the table has already been shown, you will have to validate the panel
0620: * yourself.
0621: */
0622: public void setShowRowNumbers(boolean show) {
0623: if (show == showRowNumbers)
0624: return;
0625: showRowNumbers = show;
0626: if (showRowNumbers) {
0627: canvas.makeRowLabelCanvas();
0628: add(canvas.rowLabelCanvas, BorderLayout.WEST);
0629: } else {
0630: remove(canvas.rowLabelCanvas);
0631: canvas.rowLabelCanvas = null;
0632: }
0633: }
0634:
0635: /**
0636: * Returns the color that is used as a background for row numbers and column titles.
0637: */
0638: public Color getLabelBackground() {
0639: return labelBackground;
0640: }
0641:
0642: /**
0643: * Set the color to be used as a background for row numbers and column titles.
0644: * The default is a very light gray.
0645: */
0646: public void setLabelBackground(Color color) {
0647: if (color != null)
0648: labelBackground = color;
0649: }
0650:
0651: /**
0652: * Returns the color that is used as a background for cells in the table.
0653: */
0654: public Color getCellBackground() {
0655: return cellBackground;
0656: }
0657:
0658: /**
0659: * Set the color to be used as a background for cells in the table.
0660: * The default is a light yellow.
0661: */
0662: public void setCellBackground(Color color) {
0663: if (color != null)
0664: cellBackground = color;
0665: }
0666:
0667: /**
0668: * Returns the color that is used for blank areas in the table, below the
0669: * rows of cells.
0670: */
0671: public Color getBlankBackground() {
0672: return blankBackground;
0673: }
0674:
0675: /**
0676: * Get the color to be used as a background blank areas in the table, below the
0677: * rows of cells. The default is a gray.
0678: */
0679: public void setBlankBackground(Color color) {
0680: if (color != null)
0681: blankBackground = color;
0682: }
0683:
0684: /**
0685: * Returns the color that is used for the lines between cells in the table.
0686: */
0687: public Color getGridColor() {
0688: return gridColor;
0689: }
0690:
0691: /**
0692: * Get the color to be used for the lines between cells in the table.
0693: * The default is a blue.
0694: */
0695: public void setGridColor(Color color) {
0696: if (color != null)
0697: gridColor = color;
0698: }
0699:
0700: /**
0701: * Read data for table from the specified Reader. One row is filled
0702: * from each non-empty line of input. The line should contain
0703: * numbers separated by spaces/tabs/commas. The word "undefined"
0704: * can be used to represent an empty cell. Otherwise, if a non-number is
0705: * encountered, an error occurs. If not enough numbers are
0706: * found on a line, the extra columns are filled with empties. After
0707: * filling all columns, extra data on the line is ignored.
0708: * Data currently in the table is removed and replaced (if no error
0709: * occurs during reading). In the case of an error, if throwErrors is
0710: * true, then a JCMError is thrown; if throwErrors is false, no
0711: * error is thrown, but the return value is false. If no error occurs,
0712: * the return value is true. If an error occurs, the previous data
0713: * in the table is left unchanged.
0714: */
0715: public boolean readFromStream(Reader in) {
0716: Vector newRows = new Vector();
0717: int cols = columnCount;
0718: try {
0719: StreamTokenizer tokenizer = new StreamTokenizer(in);
0720: tokenizer.resetSyntax();
0721: tokenizer.eolIsSignificant(true);
0722: tokenizer.whitespaceChars(',', ',');
0723: tokenizer.whitespaceChars(' ', ' ');
0724: tokenizer.whitespaceChars('\t', '\t');
0725: tokenizer.wordChars('a', 'z');
0726: tokenizer.wordChars('A', 'Z');
0727: tokenizer.wordChars('0', '9');
0728: tokenizer.wordChars('.', '.');
0729: tokenizer.wordChars('+', '+');
0730: tokenizer.wordChars('-', '-');
0731: int token = tokenizer.nextToken();
0732: while (true) {
0733: while (token == StreamTokenizer.TT_EOL)
0734: // ignore empty lines
0735: token = tokenizer.nextToken();
0736: if (token == StreamTokenizer.TT_EOF)
0737: break;
0738: double[] row = new double[cols];
0739: for (int i = 0; i < cols; i++) {
0740: if (token == StreamTokenizer.TT_EOL
0741: || token == StreamTokenizer.TT_EOF)
0742: row[i] = Double.NaN;
0743: else if (token == StreamTokenizer.TT_WORD) {
0744: if (tokenizer.sval
0745: .equalsIgnoreCase("undefined"))
0746: row[i] = Double.NaN;
0747: else {
0748: try {
0749: Double d = new Double(tokenizer.sval);
0750: row[i] = d.doubleValue();
0751: } catch (NumberFormatException e) {
0752: throw new IOException(
0753: "Illegal non-numeric data ("
0754: + tokenizer.sval
0755: + ") encountered.");
0756: }
0757: }
0758: token = tokenizer.nextToken();
0759: } else
0760: throw new IOException(
0761: "Illegal non-numeric data encountered.");
0762: }
0763: newRows.addElement(row);
0764: while (token != StreamTokenizer.TT_EOL
0765: && token != StreamTokenizer.TT_EOF)
0766: token = tokenizer.nextToken();
0767: }
0768: if (rows.size() == 0)
0769: throw new IOException("Empty data was found.");
0770: } catch (Exception e) {
0771: if (throwErrors)
0772: throw new JCMError("Error while reading data: " + e,
0773: this );
0774: return false;
0775: }
0776: canvas.setActive(0, 0);
0777: rows = newRows;
0778: rowStrings = new Vector();
0779: for (int i = 0; i < rows.size(); i++) {
0780: String[] s = new String[cols];
0781: double[] d = (double[]) rows.elementAt(i);
0782: for (int col = 0; col < cols; col++)
0783: if (Double.isNaN(d[col]))
0784: s[col] = null;
0785: else
0786: s[col] = NumUtils.realToString(d[col]);
0787: rowStrings.addElement(s);
0788: }
0789: canvas.input.setText(((String[]) rowStrings.elementAt(0))[0]);
0790: if (canvas.hScroll != null)
0791: canvas.hScroll.setValue(0);
0792: canvas.vScroll.setValue(0);
0793: canvas.checkScroll();
0794: canvas.repaint();
0795: if (canvas.rowLabelCanvas != null)
0796: canvas.rowLabelCanvas.repaint();
0797: if (canvas.columnLabelCanvas != null)
0798: canvas.columnLabelCanvas.repaint();
0799: serialNumber++;
0800: return true;
0801: }
0802:
0803: //------------------------------ private nested classes -------------------------------
0804:
0805: private class InputBox extends TextField {
0806: // An object of type InputBox is used for user input
0807: // of numbers in the table. There is only one input box
0808: // and it moves around.
0809: InputBox() {
0810: super (12);
0811: setBackground(Color.white);
0812: setForeground(Color.black);
0813: enableEvents(AWTEvent.KEY_EVENT_MASK
0814: + AWTEvent.MOUSE_EVENT_MASK);
0815: }
0816:
0817: public void processKeyEvent(KeyEvent evt) {
0818: if (evt.getID() == KeyEvent.KEY_PRESSED) {
0819: int ch = evt.getKeyCode();
0820: char chr = evt.getKeyChar();
0821: boolean use = (chr != 0 && Character.isDigit(chr)
0822: || chr == '.' || chr == 'E' || chr == '-'
0823: || chr == '+' || chr == 'e')
0824: || ch == KeyEvent.VK_DELETE
0825: || ch == KeyEvent.VK_BACK_SPACE;
0826: boolean useControl = use || chr == 0;
0827: if (!useControl || ch == KeyEvent.VK_ENTER
0828: || ch == KeyEvent.VK_DOWN
0829: || ch == KeyEvent.VK_UP
0830: || ch == KeyEvent.VK_TAB) {
0831: if (ch == KeyEvent.VK_ENTER
0832: || ch == KeyEvent.VK_DOWN)
0833: canvas.doRowDown();
0834: else if (ch == KeyEvent.VK_UP)
0835: canvas.doRowUp();
0836: else if (ch == KeyEvent.VK_TAB)
0837: canvas.doColumnRight();
0838: /* else
0839: Toolkit.getDefaultToolkit().beep(); */
0840: evt.consume();
0841: } else if (ch == KeyEvent.VK_LEFT
0842: && getCaretPosition() == 0) {
0843: canvas.doColumnLeft();
0844: evt.consume();
0845: } else if (ch == KeyEvent.VK_RIGHT
0846: && getCaretPosition() == getText().length()) {
0847: canvas.doColumnRight();
0848: evt.consume();
0849: }
0850: }
0851: super .processKeyEvent(evt);
0852: }
0853:
0854: public void processMouseEvent(MouseEvent evt) {
0855: if (evt.getID() == MouseEvent.MOUSE_PRESSED)
0856: canvas.ensureActiveVisible();
0857: super .processMouseEvent(evt);
0858: }
0859: } // end nested class InputBox
0860:
0861: private class DisplayPanel extends Panel implements TextListener,
0862: MouseListener, AdjustmentListener, ComponentListener {
0863:
0864: // An object of this class is the actual table seen by the user.
0865: // The panel itself is just the grid of cells. The row and column
0866: // labels and the scroll bars are variables in the DisplayPanel
0867: // ojbect, but they are added to the containing Panel in the
0868: // constructor for DataTableInput above.
0869:
0870: InputBox input; // Text field for user input, from nested class defined above.
0871:
0872: int activeRow = 0, activeColumn = 0; // Where the Input box is.
0873:
0874: int rowHeight = -1, columnWidth; // Size of each cell. rowHeight = -1 indiacates
0875: // the size is not yet known.
0876:
0877: Scrollbar hScroll, vScroll; // for scrolling through the grid of cells.
0878:
0879: Canvas rowLabelCanvas, columnLabelCanvas; // These canvasses hold the row
0880: // and column lables and are displayed
0881: // to the left of and above the grid of cells.
0882: // They scroll along with the grid.
0883: // They are null and are enabled by
0884: // methods setShowRowNumbers and setShowColumnTitles
0885:
0886: boolean lastRowAutoAdded; // True if last row was auto added.
0887:
0888: // If the user leaves this row while
0889: // it is still empty, it will be auto deleted.
0890: // Empty rows added with the addRows() method
0891: // are not deleted in this way.
0892:
0893: DisplayPanel() {
0894: setBackground(cellBackground);
0895: input = new InputBox();
0896: vScroll = new Scrollbar(Scrollbar.VERTICAL);
0897: vScroll.setBackground(Color.lightGray);
0898: input.addTextListener(this );
0899: vScroll.addAdjustmentListener(this );
0900: addMouseListener(this );
0901: setLayout(null);
0902: add(input);
0903: addComponentListener(this );
0904: }
0905:
0906: void makeRowLabelCanvas() {
0907: rowLabelCanvas = new Canvas() { // canvas for showing row labels
0908: public void paint(Graphics g) {
0909: int topRow = vScroll.getValue() / rowHeight;
0910: int rowCt = getSize().height / rowHeight + 1;
0911: int tableRows = rows.size();
0912: FontMetrics fm = g.getFontMetrics();
0913: int textOffset = (rowHeight + fm.getAscent()) / 2;
0914: int vScrollVal = vScroll.getValue();
0915: for (int i = topRow; i < rowCt + topRow
0916: && i < tableRows; i++) {
0917: String rs = "" + (i + 1);
0918: int os = (getSize().width - fm.stringWidth(rs)) / 2;
0919: g.drawString(rs, os, textOffset + rowHeight * i
0920: - vScrollVal);
0921: }
0922: }
0923:
0924: public Dimension getPreferredSize() {
0925: return new Dimension(35, 50);
0926: }
0927: };
0928: rowLabelCanvas.setBackground(labelBackground);
0929: }
0930:
0931: void makeColumnLabelCanvas() {
0932: columnLabelCanvas = new Canvas() { // canvas for showing column labels
0933: public void paint(Graphics g) {
0934: int leftColumn = 0;
0935: if (hScroll != null)
0936: leftColumn = hScroll.getValue() / columnWidth;
0937: int blank = (rowLabelCanvas == null) ? 0 : 35; // width of rowLabelCanvas
0938: int columnCt = (getSize().width - blank)
0939: / columnWidth + 1;
0940: FontMetrics fm = g.getFontMetrics();
0941: int textOffset = (getSize().height + fm.getAscent()) / 2;
0942: int hScrollVal = hScroll == null ? 0 : hScroll
0943: .getValue();
0944: for (int i = leftColumn; i < leftColumn + columnCt
0945: && i < columnCount; i++) {
0946: String s = getColumnName(i);
0947: int os = (columnWidth - fm.stringWidth(s)) / 2;
0948: g.drawString(s, blank + i * columnWidth + os
0949: - hScrollVal, textOffset);
0950: }
0951: g.setColor(Color.gray);
0952: g.fillRect(0, 0, blank, getSize().height);
0953: }
0954:
0955: public Dimension getPreferredSize() {
0956: return new Dimension(50, 20);
0957: }
0958: };
0959: columnLabelCanvas.setBackground(labelBackground);
0960: }
0961:
0962: public void addNotify() {
0963: // Determine the size of a cell, which is based on the preferred size
0964: // of the input box (which can't be determined until after the peer
0965: // is added. (I hope this works on all platforms!)
0966: super .addNotify();
0967: if (rowHeight != -1)
0968: return;
0969: Dimension size = input.getPreferredSize();
0970: rowHeight = size.height - 1;
0971: columnWidth = size.width - 1;
0972: input.setBounds(1, 1, columnWidth + 1, rowHeight + 1);
0973: }
0974:
0975: public void update(Graphics g) {
0976: // Don't fill in with background before painting.
0977: paint(g);
0978: }
0979:
0980: public void paint(Graphics g) {
0981: // Draw the grid of cells, in position based on scroll bar values.
0982: int hScrollVal = (hScroll == null) ? 0 : hScroll.getValue();
0983: int vScrollVal = vScroll.getValue();
0984: int width = getSize().width; // width and height of component
0985: int height = getSize().height;
0986: int tableWidth = columnCount * columnWidth + 2;
0987: int tableHeight = rows.size() * rowHeight + 2;
0988: int tableRows = rows.size(); // Number of rows in table
0989: Rectangle clip = g.getClipBounds(); // Try to avoid uncessary painting by checking clip rect.
0990: int topRow, rowCt, leftColumn, columnCt;
0991: if (clip != null) { // change data about included rows, columns
0992: topRow = (vScrollVal + clip.y) / rowHeight;
0993: rowCt = clip.height / rowHeight + 1;
0994: leftColumn = (hScrollVal + clip.x) / columnWidth;
0995: columnCt = clip.width / columnWidth + 1;
0996: } else {
0997: topRow = vScrollVal / rowHeight; // top visible row
0998: rowCt = height / rowHeight + 1; // num of rows possibly visible
0999: leftColumn = hScrollVal / columnWidth;// leftmost visible column
1000: columnCt = width / columnWidth + 1; // num of columns visible
1001: }
1002: FontMetrics fm = g.getFontMetrics();
1003: int textOffset = (rowHeight + fm.getAscent()) / 2; // from top of box to text baseline
1004: for (int i = topRow; i < topRow + rowCt && i < tableRows; i++) {
1005: String[] contents = (String[]) rowStrings.elementAt(i);
1006: for (int c = 0; c < columnCount; c++)
1007: if (c != activeColumn || i != activeRow) {
1008: g.setColor(cellBackground);
1009: g.fillRect(1 + c * columnWidth - hScrollVal, 1
1010: + i * rowHeight - vScrollVal,
1011: columnWidth, rowHeight);
1012: g.setColor(getForeground());
1013: if (contents != null && contents[c] != null
1014: && contents[c].length() > 0) {
1015: String s = contents[c];
1016: g.drawString(s, c * columnWidth + 5
1017: - hScrollVal, textOffset + i
1018: * rowHeight - vScrollVal);
1019: }
1020: }
1021: }
1022: if (width > tableWidth) {
1023: g.setColor(blankBackground);
1024: g.fillRect(tableWidth, 0, width - tableWidth, height);
1025: }
1026: if (height > tableHeight) {
1027: g.setColor(blankBackground);
1028: g.fillRect(0, tableHeight, width, height - tableHeight);
1029: }
1030: g.setColor(gridColor);
1031: g.drawRect(0, 0, tableWidth, tableHeight);
1032: g.drawRect(1, 1, tableWidth - 2, tableHeight - 2);
1033: for (int i = -1; i < topRow + rowCt && i < tableRows; i++)
1034: g.drawLine(0, 1 + (i + 1) * rowHeight - vScrollVal,
1035: tableWidth - 1, 1 + (i + 1) * rowHeight
1036: - vScrollVal);
1037: for (int j = 0; j <= columnCount; j++)
1038: g.drawLine(1 + j * columnWidth - hScrollVal, 0, 1 + j
1039: * columnWidth - hScrollVal, tableHeight - 1);
1040: }
1041:
1042: void setActive(int row, int column) {
1043: // Move the input box to the specified row and column, and
1044: // move the focus to the input box.
1045: if (row != activeRow || column != columnWidth) {
1046: int topOffset = vScroll.getValue();
1047: int leftOffset = (hScroll == null) ? 0 : hScroll
1048: .getValue();
1049: int y = -topOffset + row * rowHeight + 1;
1050: int x = -leftOffset + column * columnWidth + 1;
1051: input.setLocation(x, y);
1052: activeRow = row;
1053: activeColumn = column;
1054: String[] contents = (String[]) rowStrings
1055: .elementAt(activeRow);
1056: if (contents == null || contents[activeColumn] == null)
1057: input.setText("");
1058: else
1059: input.setText(contents[activeColumn]);
1060: }
1061: ensureActiveVisible();
1062: input.selectAll();
1063: input.requestFocus();
1064: }
1065:
1066: void doRowDown() {
1067: // Move active box down one row. Create a new row if in the
1068: // last row, that row is non-empty, and autoAddRows is true.
1069: int tableRows = rows.size();
1070: if (activeRow == tableRows - 1 && autoAddRows
1071: && rows.elementAt(tableRows - 1) != null) {
1072: addRows(1, tableRows);
1073: lastRowAutoAdded = true;
1074: }
1075: if (activeRow < rows.size() - 1)
1076: setActive(activeRow + 1, activeColumn);
1077: else {
1078: ensureActiveVisible();
1079: input.requestFocus();
1080: }
1081: }
1082:
1083: void doRowUp() {
1084: // Move up one row. If leaving an empty row that was autoadded, delete it.
1085: if (activeRow == 0)
1086: return;
1087: setActive(activeRow - 1, activeColumn);
1088: if (autoAddRows && lastRowAutoAdded == true
1089: && activeRow == rows.size() - 2
1090: && rows.elementAt(activeRow + 1) == null) {
1091: // delete empty row from bottom of table
1092: rows.removeElementAt(rows.size() - 1);
1093: rowStrings.removeElementAt(rowStrings.size() - 1);
1094: checkScroll();
1095: repaint();
1096: if (rowLabelCanvas != null)
1097: rowLabelCanvas.repaint();
1098: input.requestFocus();
1099: }
1100: lastRowAutoAdded = false;
1101: }
1102:
1103: void doColumnRight() {
1104: // Move active box right to the next column, possibly
1105: // wrapping around to the first column.
1106: int c = activeColumn + 1;
1107: if (c >= columnCount)
1108: c = 0;
1109: setActive(activeRow, c);
1110: }
1111:
1112: void doColumnLeft() {
1113: // Move active box left to the next column, possibly
1114: // wrapping around to the last column.
1115: int c = activeColumn - 1;
1116: if (c < 0)
1117: c = columnCount - 1;
1118: setActive(activeRow, c);
1119: }
1120:
1121: void ensureActiveVisible() {
1122: // Make sure that the entire input box is visible.
1123: int x = columnWidth * activeColumn + 1;
1124: int y = rowHeight * activeRow + 1;
1125: int visibleLeft = (hScroll == null) ? 0 : hScroll
1126: .getValue();
1127: int visibleTop = vScroll.getValue();
1128: int visibleRight = visibleLeft + getSize().width;
1129: int visibleBottom = visibleTop + getSize().height;
1130: int offsetX = 0;
1131: int offsetY = 0;
1132: if (x + columnWidth > visibleRight)
1133: offsetX = -(x + columnWidth - visibleRight);
1134: if (x < visibleLeft)
1135: offsetX = visibleLeft - x;
1136: if (y + rowHeight > visibleBottom)
1137: offsetY = -(y + rowHeight - visibleBottom);
1138: if (y < visibleTop)
1139: offsetY = visibleTop - y;
1140: if (offsetX == 0 && offsetY == 0)
1141: return;
1142: if (offsetX != 0) {
1143: if (hScroll != null)
1144: hScroll.setValue(visibleLeft - offsetX);
1145: if (columnLabelCanvas != null)
1146: columnLabelCanvas.repaint();
1147: }
1148: if (offsetY != 0) {
1149: vScroll.setValue(visibleTop - offsetY);
1150: if (rowLabelCanvas != null)
1151: rowLabelCanvas.repaint();
1152: }
1153: input.setLocation(x
1154: - (hScroll == null ? 0 : hScroll.getValue()), y
1155: - vScroll.getValue());
1156: repaint();
1157: }
1158:
1159: void addRows(int num, int before) {
1160: // Add specified number of rows to table, before row
1161: // number before. If before is after the last existing
1162: // row, the rows are added at the end of the table.
1163: serialNumber++;
1164: if (num <= 0)
1165: return;
1166: if (before >= rows.size()) {
1167: for (int i = 0; i < num; i++) {
1168: rows.addElement(null);
1169: rowStrings.addElement(null);
1170: lastRowAutoAdded = false;
1171: }
1172: } else {
1173: if (before < 0)
1174: before = 0;
1175: for (int i = 0; i < num; i++) {
1176: rows.insertElementAt(null, before);
1177: rowStrings.insertElementAt(null, before);
1178: }
1179: if (activeRow >= before) { // data in active cell changes
1180: String[] vals = (String[]) rowStrings
1181: .elementAt(activeRow);
1182: if (vals == null || vals[activeColumn] == null)
1183: input.setText("");
1184: else
1185: input.setText(vals[activeColumn]);
1186: }
1187: }
1188: checkScroll();
1189: repaint();
1190: if (rowLabelCanvas != null)
1191: rowLabelCanvas.repaint();
1192: }
1193:
1194: void setValue(int column, int row, double value) {
1195: // set the value in the column at given row and column.
1196: // Note that row numbers start at 1 in this method!!
1197: // Row and column numbers are assumed to be in legal range!!
1198: String num = NumUtils.realToString(value);
1199: setRowData(row - 1, column, num, value);
1200: if (column == activeColumn && row - 1 == activeRow)
1201: input.setText(num);
1202: else {
1203: repaintItem(row - 1, column);
1204: }
1205: }
1206:
1207: double getValue(int column, int row) {
1208: // Get the value from the table in the given row and column.
1209: // Note that row numbers start at 1 in this method!!
1210: // Row and column numbers are assumed to be in legal range!!
1211: if (rows.elementAt(row - 1) == null)
1212: return emptyCellValue;
1213: else {
1214: double d = ((double[]) rows.elementAt(row - 1))[column];
1215: if (!Double.isNaN(d))
1216: return d;
1217: else {
1218: String val = ((String[]) rowStrings
1219: .elementAt(row - 1))[column];
1220: if (val == null || val.length() == 0)
1221: return emptyCellValue;
1222: else // val is "bad input", the only other possibility if d is NaN
1223: if (throwErrors)
1224: throw new JCMError(
1225: "Invalid numerical input in data table, column \""
1226: + getColumnName(column)
1227: + "\", row " + row + ".", this );
1228: else
1229: return Double.NaN;
1230: }
1231: }
1232: }
1233:
1234: public void textValueChanged(TextEvent txt) {
1235: // From TextListener interface. When text in input box changes,
1236: // change the stored string and stored value for that position to match.
1237: String num = input.getText().trim();
1238: if (num.length() == 0)
1239: setRowData(activeRow, activeColumn, num, Double.NaN);
1240: else {
1241: double x;
1242: try {
1243: Double d = new Double(num);
1244: x = d.doubleValue();
1245: } catch (NumberFormatException e) {
1246: x = Double.NaN;
1247: }
1248: if (Double.isNaN(x))
1249: setRowData(activeRow, activeColumn, "bad input", x);
1250: else
1251: setRowData(activeRow, activeColumn, num, x);
1252: }
1253: }
1254:
1255: void setRowData(int row, int col, String num, double val) {
1256: // puts num, val into rows, rowStrings vectors at position row,col
1257: // Empty rows are always represented by null's in the rowVals
1258: // and rowStrings vectores. This requires a bit of care.
1259: serialNumber++;
1260: double[] rowVals = (double[]) rows.elementAt(row);
1261: String[] rowStr = (String[]) rowStrings.elementAt(row);
1262: if (num.length() == 0) {
1263: if (rowStr == null || rowStr[col] == null)
1264: return;
1265: rowStr[col] = null;
1266: rowVals[col] = Double.NaN;
1267: boolean empty = true;
1268: for (int i = 0; i < rowStr.length; i++)
1269: if (rowStr[i] != null) {
1270: empty = false;
1271: break;
1272: }
1273: if (empty) {
1274: rows.setElementAt(null, row);
1275: rowStrings.setElementAt(null, row);
1276: }
1277: } else {
1278: if (num.length() > 12)
1279: num = NumUtils.realToString(val, 12);
1280: if (rowStr == null) {
1281: int ct = columnCount;
1282: rowVals = new double[ct];
1283: rowStr = new String[ct];
1284: for (int i = 0; i < ct; i++)
1285: rowVals[i] = Double.NaN;
1286: rows.setElementAt(rowVals, row);
1287: rowStrings.setElementAt(rowStr, row);
1288: }
1289: rowStr[col] = num;
1290: rowVals[col] = val;
1291: }
1292: }
1293:
1294: protected void repaintItem(int row, int column) {
1295: // forces a repaint for just the specified cell
1296: int y = row * rowHeight - vScroll.getValue();
1297: int x = column * columnWidth;
1298: if (hScroll != null)
1299: x -= hScroll.getValue();
1300: repaint(x + 1, y + 1, columnWidth - 1, rowHeight - 1);
1301: }
1302:
1303: public void adjustmentValueChanged(AdjustmentEvent evt) {
1304: // From the AdjustmentListener interface. React to
1305: // change in scroll positions.
1306: repaint();
1307: if (evt.getSource() == vScroll) {
1308: if (rowLabelCanvas != null)
1309: rowLabelCanvas.repaint();
1310: } else {
1311: if (columnLabelCanvas != null)
1312: columnLabelCanvas.repaint();
1313: }
1314: int x = columnWidth * activeColumn + 1;
1315: if (hScroll != null)
1316: x -= hScroll.getValue();
1317: int y = rowHeight * activeRow + 1 - vScroll.getValue();
1318: input.setLocation(x, y);
1319: }
1320:
1321: public void mousePressed(MouseEvent evt) {
1322: // From the MouseListener interface. Move the active
1323: // cell to an input cell close to mouse click.
1324: int hOffset = (hScroll == null) ? 0 : hScroll.getValue();
1325: int vOffset = vScroll.getValue();
1326: int row = (evt.getY() + vOffset - 1) / rowHeight;
1327: if (row < 0)
1328: row = 0;
1329: else if (row >= rows.size())
1330: row = rows.size() - 1;
1331: int col = (evt.getX() + hOffset - 1) / columnWidth;
1332: if (col < 0)
1333: col = 0;
1334: else if (col >= columnCount)
1335: col = columnCount - 1;
1336: if (row == activeRow && col == activeColumn)
1337: ensureActiveVisible(); // want to avoid doing selectAll if box doesn't move
1338: else
1339: setActive(row, col);
1340: int emptyRow = rows.size() - 1;
1341: if (!lastRowAutoAdded || emptyRow == row)
1342: return;
1343: lastRowAutoAdded = false;
1344: if (rows.elementAt(emptyRow) != null)
1345: return;
1346: rows.removeElementAt(emptyRow);
1347: rowStrings.removeElementAt(emptyRow);
1348: checkScroll();
1349: repaint();
1350: if (rowLabelCanvas != null)
1351: rowLabelCanvas.repaint();
1352: }
1353:
1354: public void componentResized(ComponentEvent evt) {
1355: // From ComponentListener interface. Fix scroll bars after resize.
1356: checkScroll();
1357: }
1358:
1359: void checkScroll() {
1360: // Make sure scoll bars are OK after resize, adding rows, etc.
1361: int width = DisplayPanel.this .getSize().width;
1362: int height = DisplayPanel.this .getSize().height;
1363: if (rowHeight == -1 || width <= 1)
1364: return;
1365: int tableWidth = columnWidth * columnCount + 2;
1366: int tableHeight = rowHeight * rows.size() + 2;
1367: int oldTop = vScroll.getValue();
1368: int oldLeft = (hScroll == null) ? 0 : hScroll.getValue();
1369: boolean revalidate = false;
1370: if (width >= tableWidth - 2) {
1371: if (hScroll != null) {
1372: int scrollHeight = hScroll.getPreferredSize().height;
1373: DataTableInput.this .remove(hScroll);
1374: hScroll = null;
1375: height += scrollHeight;
1376: revalidate = true;
1377: }
1378: } else {
1379: if (hScroll == null) {
1380: hScroll = new Scrollbar(Scrollbar.HORIZONTAL);
1381: hScroll.setBackground(Color.lightGray);
1382: int scrollHeight = hScroll.getPreferredSize().height;
1383: height -= scrollHeight;
1384: DataTableInput.this
1385: .add(hScroll, BorderLayout.SOUTH);
1386: hScroll.addAdjustmentListener(this );
1387: revalidate = true;
1388: }
1389: if (oldLeft > tableWidth - width)
1390: hScroll.setValues(tableWidth - width, width, 0,
1391: tableWidth);
1392: else
1393: hScroll.setValues(oldLeft, width, 0, tableWidth);
1394: hScroll.setUnitIncrement(columnWidth / 4);
1395: if (width > 1)
1396: hScroll.setBlockIncrement((3 * width) / 4);
1397: }
1398: if (height >= tableHeight - 2) {
1399: vScroll.setEnabled(false);
1400: vScroll.setValues(0, 1, 0, 1);
1401: } else {
1402: if (oldTop > tableHeight - height)
1403: vScroll.setValues(tableHeight - height, height, 0,
1404: tableHeight);
1405: else
1406: vScroll.setValues(oldTop, height, 0, tableHeight);
1407: vScroll.setUnitIncrement(rowHeight);
1408: if (height > 1)
1409: vScroll.setBlockIncrement((3 * height) / 4);
1410: vScroll.setEnabled(true);
1411: }
1412: int x = columnWidth * activeColumn + 1;
1413: if (hScroll != null)
1414: x -= hScroll.getValue();
1415: int y = rowHeight * activeRow + 1 - vScroll.getValue();
1416: input.setLocation(x, y);
1417: if (revalidate)
1418: DataTableInput.this .validate();
1419: }
1420:
1421: public Dimension getPreferredSize() {
1422: if (rowHeight == -1)
1423: return new Dimension(350, 200);
1424: else if (columnCount >= 4)
1425: return new Dimension(4 * columnWidth + 2,
1426: 6 * rowHeight + 2);
1427: else
1428: return new Dimension(columnCount * columnWidth + 2,
1429: 6 * rowHeight + 2);
1430: }
1431:
1432: public void mouseEntered(MouseEvent evt) {
1433: } // Other methods from listener interfaces.
1434:
1435: public void mouseExited(MouseEvent evt) {
1436: }
1437:
1438: public void mouseClicked(MouseEvent evt) {
1439: }
1440:
1441: public void mouseReleased(MouseEvent evt) {
1442: }
1443:
1444: public void componentHidden(ComponentEvent evt) {
1445: }
1446:
1447: public void componentShown(ComponentEvent evt) {
1448: }
1449:
1450: public void componentMoved(ComponentEvent evt) {
1451: }
1452:
1453: } // end nested class Display Panel
1454:
1455: private class DTEC implements ExpressionCommand {
1456:
1457: // This is used in the doParse() method. When a reference to a DataInputTable
1458: // is found by a parser, the doParse() method will add an object of this
1459: // type to the ExpressionProgram that the parseris producing. A DTEC represents
1460: // a sub-exprssion such as "data.A(3)" or "data.sum(A^2)"
1461:
1462: ExpressionProgram prog; // The expression inside the parentheses
1463:
1464: int command; // The column number for an expression such as "data.A(3)" which
1465:
1466: // represents the value of a cell in column "A". For the sum
1467: // function, command is -1. For the count funtion, it is -2.
1468:
1469: DTEC(int command, ExpressionProgram prog) {
1470: this .command = command;
1471: this .prog = prog;
1472: }
1473:
1474: public void apply(StackOfDouble stack, Cases cases) {
1475: if (command >= 0) { // Reference to a column. Value of prog gives row number.
1476: double loc = prog.getVal();
1477: if (Double.isNaN(loc) || loc < 0.5
1478: || loc >= rows.size() + 0.5)
1479: stack.push(Double.NaN);
1480: else
1481: stack.push(canvas.getValue(command,
1482: (int) (loc + 0.5)));
1483: } else if (command == -1) { // sum of the prog expression for all rows in the table.
1484: double sum = 0;
1485: int top = getNonEmptyRowCount();
1486: for (int row = 1; row <= top; row++) {
1487: setCurrentRowNumber(row);
1488: sum += prog.getVal();
1489: }
1490: stack.push(sum);
1491: } else if (command == -2) { // the count of rows in the table
1492: stack.push(getNonEmptyRowCount());
1493: }
1494: }
1495:
1496: public void compileDerivative(ExpressionProgram prog,
1497: int myIndex, ExpressionProgram deriv, Variable wrt) {
1498: if (command != -1) {
1499: deriv.addConstant(0);
1500: } else {
1501: ExpressionProgram d = (ExpressionProgram) this .prog
1502: .derivative(wrt);
1503: deriv.addCommandObject(new DTEC(command, d));
1504: }
1505: }
1506:
1507: public int extent(ExpressionProgram prog, int myIndex) {
1508: return 1;
1509: }
1510:
1511: public boolean dependsOn(Variable x) {
1512: if (command == -2)
1513: return false;
1514: else
1515: return prog.dependsOn(x);
1516: }
1517:
1518: public void appendOutputString(ExpressionProgram prog,
1519: int myIndex, StringBuffer buffer) {
1520: buffer.append(getName());
1521: buffer.append('.');
1522: if (command == -2)
1523: buffer.append("count");
1524: else if (command == -1)
1525: buffer.append("sum");
1526: else
1527: buffer.append(getColumnName(command));
1528: buffer.append("(");
1529: if (command != -2)
1530: buffer.append(this .prog.toString());
1531: buffer.append(")");
1532: }
1533:
1534: } // end nested class DTEC
1535:
1536: } // end class DataTableInput
|