dmolony-DiskBrowser/src/com/bytezone/diskbrowser/visicalc/Sheet.java

452 lines
13 KiB
Java
Raw Normal View History

2016-03-04 03:56:28 +00:00
package com.bytezone.diskbrowser.visicalc;
2015-06-01 09:35:51 +00:00
2016-03-08 09:39:35 +00:00
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
2015-06-01 09:35:51 +00:00
import java.util.regex.Matcher;
import java.util.regex.Pattern;
2016-02-24 21:11:14 +00:00
import com.bytezone.diskbrowser.utilities.HexFormatter;
2015-06-01 09:35:51 +00:00
2016-03-15 20:06:04 +00:00
public class Sheet
2015-06-01 09:35:51 +00:00
{
2016-02-04 00:31:44 +00:00
private static final Pattern addressPattern =
2016-03-08 09:39:35 +00:00
Pattern.compile ("([AB]?[A-Z])([0-9]{1,3}):");
2016-03-15 20:27:47 +00:00
private static final byte END_OF_LINE_TOKEN = (byte) 0x8D;
2016-03-16 06:15:39 +00:00
private static final int FORMAT_LENGTH = 3;
2015-06-01 09:35:51 +00:00
2016-03-15 20:06:04 +00:00
private final Map<Integer, Cell> rowOrderCells = new TreeMap<Integer, Cell> ();
private final Map<Integer, Cell> columnOrderCells = new TreeMap<Integer, Cell> ();
2016-03-11 02:54:31 +00:00
private final List<String> lines = new ArrayList<String> ();
2015-06-01 09:35:51 +00:00
2016-03-04 05:27:14 +00:00
private final Map<Integer, Integer> columnWidths = new TreeMap<Integer, Integer> ();
2017-03-05 10:42:27 +00:00
private int columnWidth = 9;
2016-03-15 20:27:47 +00:00
2017-03-05 10:42:27 +00:00
private char globalFormat = ' ';
2016-03-15 20:27:47 +00:00
private char recalculation = 'A'; // auto/manual
2016-03-15 20:06:04 +00:00
private char recalculationOrder = 'C'; // row/column
2016-03-15 20:27:47 +00:00
2017-03-05 10:42:27 +00:00
private int minColumn = 9999;
private int maxColumn;
private int minRow = 9999;
private int maxRow;
2016-03-04 05:27:14 +00:00
2015-06-01 09:35:51 +00:00
// Maximum cell = BK254
//Commands:
// /G<address> goto
// /B blank the cell
// /C clear and home
// A-Z label (" to force a label)
// 0-9.+-()*/@# value (+ to force a value)
// /S Storage (ISLDWR)
// /SI Storage init
// /SS Storage Save
// /SL Storage Load
// /SD Storage Delete
// /SW Storage Write (to cassette)
// /SR Storage Read (from cassette)
// /R Replicate
// /G Global (CORF)
// /GF Global Format (DGILR$*)
// /GFI Global Format Integer
// /GF$ Global Format Currency
// /GC Global Column <width>
// /GR Global
2016-03-08 09:39:35 +00:00
// /GRA Recalculation Auto
2015-06-01 09:35:51 +00:00
// /GO Global
2016-03-08 09:39:35 +00:00
// /GOC Calculation Order - Columns first
2017-02-26 10:44:10 +00:00
// /GOR Calculation Order - Rows first
2015-06-01 09:35:51 +00:00
// /T Titles (HVBN)
// /TH fix Horizontal Titles
// /TV fix Vertical Titles
// /TB fix Both Titles
// /TN fix Neither
// /W Window (HV1SU)
// /WV Window Vertical (split on cursor column)
// /WH Window Horizontal (split on cursor row)
2016-02-04 00:31:44 +00:00
/*
from: Apple II TextFiles
http://www.textfiles.com/apple/
*----------------------*
VISICALC COMMAND CHART
BY: NIGHT HAWK
*----------------------*
/B SET AN ENTRY TO BLANK
/C CLEARS THE SHEET, SETTING ALL ENTRIES TO BLANK
2016-03-14 08:58:54 +00:00
/D DELETE
/DR THE ROW
/DC COLUMN ON WHICH THE CURSOR LIES
2016-02-04 00:31:44 +00:00
/E ALLOWS EDITING OF THE ENTRY CONTENTS OF ANY ENTRY POSITION
BY REDISPLAYING IT ON THE EDIT LINE. USE <- -> KEYS & ESC.
2016-03-14 08:58:54 +00:00
/F FORMATS:
2016-02-04 00:31:44 +00:00
/FG GENERAL
2016-03-14 08:58:54 +00:00
/FI INTEGER
/F$ DOLLAR AND CENTS
/FL LEFT JUSTIFIED
/FR RIGHT JUSTIFIED
/F* GRAPH
/FD DEFAULT
2016-02-04 00:31:44 +00:00
/G GLOBAL COMMANDS. THESE APPLY TO THE ENTIRE SHEET OR WINDOW.
/GC SETS COLUMN WIDTH
/GF SETS THE GLOBAL DEFAULT FORMAT
/GO SETS THE ORDER OF RECALCULATION TO BE DOWN THE
COLUMNS OR ACROSS THE ROWS
/GR SETS RECALCULATION TO BE AUTOMATIC(/GRA) OR MANUAL(/GRM).
/I INSERTS A ROW(/IR) OR A COLUMN(/IC)
/M MOVES AN ENTIRE ROW OR COLUMN TO A NEW POSITION.
/P PRINT COMMAND
/R REPLICATE COMMAND
2016-03-14 08:58:54 +00:00
/S STORAGE COMMANDS
2016-03-01 22:51:56 +00:00
/SS SAVE
2017-02-25 03:56:22 +00:00
/SL LOAD (left/right arrow to scroll through catalog)
2016-03-01 22:51:56 +00:00
/SD DELETES SPECIFIED FILE ON DISK
2016-02-04 00:31:44 +00:00
/SI INITIALIZE A DISK ON SPECIFIED DRIVE
/SQ QUITS VISICALC
2016-03-14 08:58:54 +00:00
/T
/TH SETS A HORIZONTAL TITLE AREA
/TV SETS A VERTICAL TITLE AREA
/TB SET BOTH A HORIZONTAL & VERTICAL TITLE AREA
/TN RESETS THE WINDOWS TO HAVE NO TITLE AREAS
2016-02-04 00:31:44 +00:00
/V DISPLAYS VISICALC'S VERSION NUMBER ON THE PROMPT LINE
/W WINDOW CONTROL
/WH HORIZONTAL WINDOW
/WV VERTICAL WINDOW
/W1 RETURNS SCREEN TO ONE WINDOW
/WS SYNCHRONIZED WINDOWS
/WU UNSYNCHRONIZED
/- REPEATING LABEL
*/
2017-02-26 10:44:10 +00:00
// /X!/X>A3:>A7: A3:top-left cell in window, A7:cell to place cursor
2016-03-06 08:05:32 +00:00
public Sheet (byte[] buffer)
2015-06-01 09:35:51 +00:00
{
2016-03-04 05:27:14 +00:00
int last = buffer.length;
2016-03-14 08:58:54 +00:00
while (buffer[--last] == 0) // ignore trailing zeroes
2016-03-04 05:27:14 +00:00
;
2015-06-01 09:35:51 +00:00
2016-03-04 05:27:14 +00:00
int ptr = 0;
2016-03-06 23:52:46 +00:00
while (ptr < last)
2015-06-01 09:35:51 +00:00
{
2016-03-06 23:52:46 +00:00
int length = getLineLength (buffer, ptr);
String line = HexFormatter.getString (buffer, ptr, length);
2017-02-25 03:56:22 +00:00
assert !line.isEmpty ();
2016-03-06 23:52:46 +00:00
lines.add (line);
2017-02-25 03:56:22 +00:00
if (line.startsWith ("/"))
doFormat (line);
else if (line.startsWith (">")) // GOTO cell
processLine (line);
else
System.out.printf ("Error [%s]%n", line);
2016-03-06 23:52:46 +00:00
ptr += length + 1; // +1 for end-of-line token
2015-06-01 09:35:51 +00:00
}
2017-02-18 09:54:24 +00:00
// might have to keep recalculating until nothing changes??
2017-02-25 03:56:22 +00:00
if (recalculation == 'A') // auto
{
calculate (recalculationOrder);
calculate (recalculationOrder);
}
2016-03-14 08:58:54 +00:00
}
private void calculate (char order)
{
2016-03-15 20:06:04 +00:00
Map<Integer, Cell> cells = order == 'R' ? rowOrderCells : columnOrderCells;
for (Cell cell : cells.values ())
2017-03-08 09:18:59 +00:00
cell.calculate ();
2016-03-04 05:27:14 +00:00
}
2016-03-06 23:52:46 +00:00
private int getLineLength (byte[] buffer, int offset)
2016-03-04 05:27:14 +00:00
{
2016-03-06 23:52:46 +00:00
int ptr = offset;
2017-02-25 03:56:22 +00:00
while (buffer[ptr] != END_OF_LINE_TOKEN)
2016-03-04 05:27:14 +00:00
ptr++;
2016-03-06 23:52:46 +00:00
return ptr - offset;
2015-06-01 09:35:51 +00:00
}
2016-03-08 09:39:35 +00:00
private void processLine (String line)
2015-06-01 09:35:51 +00:00
{
2017-02-25 03:56:22 +00:00
Cell currentCell = null;
2016-03-08 09:39:35 +00:00
2017-02-25 03:56:22 +00:00
Matcher m = addressPattern.matcher (line); // <cell address>:<contents>
2016-03-08 09:39:35 +00:00
if (m.find ())
2015-06-01 09:35:51 +00:00
{
2016-03-08 09:39:35 +00:00
Address address = new Address (m.group (1), m.group (2));
2017-03-03 23:41:08 +00:00
currentCell = rowOrderCells.get (address.getRowKey ());
2016-03-08 09:39:35 +00:00
int pos = line.indexOf (':'); // end of cell address
line = line.substring (pos + 1); // remove address from line
if (currentCell == null)
{
currentCell = new Cell (this, address);
if (!line.startsWith ("/G"))
2016-03-15 20:27:47 +00:00
addCell (currentCell);
2016-03-08 09:39:35 +00:00
}
2015-06-01 09:35:51 +00:00
}
2016-03-08 09:39:35 +00:00
else
2015-06-01 09:35:51 +00:00
{
2016-03-08 09:39:35 +00:00
System.out.printf ("Invalid cell address: %s%n", line);
return;
2015-06-01 09:35:51 +00:00
}
2016-03-08 09:39:35 +00:00
assert currentCell != null;
2016-03-14 20:54:47 +00:00
if (line.startsWith ("/G")) // global column widths
2015-06-01 09:35:51 +00:00
{
2016-03-08 09:39:35 +00:00
if (line.charAt (2) == 'C' && line.charAt (3) == 'C')
{
int width = Integer.parseInt (line.substring (4));
2017-03-03 23:41:08 +00:00
columnWidths.put (currentCell.getAddress ().getColumn (), width);
2016-03-08 09:39:35 +00:00
}
else
System.out.printf ("Unknown Global:[%s]%n", line);
return;
2015-06-01 09:35:51 +00:00
}
2016-03-08 09:39:35 +00:00
2016-03-12 00:48:45 +00:00
// check for formatting commands
while (line.startsWith ("/"))
2015-06-01 09:35:51 +00:00
{
2017-03-05 10:42:27 +00:00
if (line.charAt (1) == '-') // repeating label
2016-03-14 20:54:47 +00:00
{
2017-02-25 03:56:22 +00:00
currentCell.setFormat (line);
line = "";
2016-03-14 20:54:47 +00:00
}
else
2017-02-25 03:56:22 +00:00
{
2017-03-05 10:42:27 +00:00
currentCell.setFormat (line.substring (0, FORMAT_LENGTH));
2017-02-25 03:56:22 +00:00
line = line.substring (FORMAT_LENGTH);
}
2015-06-01 09:35:51 +00:00
}
2017-02-25 03:56:22 +00:00
// if there is anything left it must be an expression
2016-03-08 09:39:35 +00:00
if (!line.isEmpty ())
2017-03-05 10:42:27 +00:00
currentCell.setValue (line); // expression
2016-03-11 21:56:02 +00:00
}
2016-03-15 20:27:47 +00:00
private void addCell (Cell cell)
{
2017-03-03 23:41:08 +00:00
rowOrderCells.put (cell.getAddress ().getRowKey (), cell);
columnOrderCells.put (cell.getAddress ().getColumnKey (), cell);
2016-03-15 20:27:47 +00:00
2017-03-05 10:42:27 +00:00
minRow = Math.min (minRow, cell.getAddress ().getRow ());
minColumn = Math.min (minColumn, cell.getAddress ().getColumn ());
maxRow = Math.max (maxRow, cell.getAddress ().getRow ());
maxColumn = Math.max (maxColumn, cell.getAddress ().getColumn ());
2016-03-15 20:27:47 +00:00
}
2016-03-11 21:56:02 +00:00
Cell getCell (String addressText)
{
2016-03-14 08:58:54 +00:00
return getCell (new Address (addressText));
2016-03-08 09:39:35 +00:00
}
2015-06-01 09:35:51 +00:00
2016-03-07 04:37:01 +00:00
Cell getCell (Address address)
2015-06-01 09:35:51 +00:00
{
2017-03-03 23:41:08 +00:00
Cell cell = rowOrderCells.get (address.getRowKey ());
2017-03-03 10:24:23 +00:00
if (cell == null)
{
cell = new Cell (this, address);
addCell (cell);
}
return cell;
}
boolean cellExists (Address address)
{
2017-03-03 23:41:08 +00:00
return rowOrderCells.get (address.getRowKey ()) != null;
2015-06-01 09:35:51 +00:00
}
public int size ()
{
2016-03-15 20:06:04 +00:00
return rowOrderCells.size ();
2015-06-01 09:35:51 +00:00
}
2017-02-25 03:56:22 +00:00
private void doFormat (String line)
{
switch (line.charAt (1))
{
case 'G':
2017-03-05 10:42:27 +00:00
setGlobal (line);
break;
case 'W':
2017-02-25 03:56:22 +00:00
break;
case 'X':
break;
default:
System.out.printf ("Skipping [%s]%n", line);
}
}
2017-03-05 10:42:27 +00:00
private void setGlobal (String line)
2016-03-11 02:54:31 +00:00
{
2017-03-05 10:42:27 +00:00
switch (line.charAt (2))
2016-03-11 02:54:31 +00:00
{
2017-03-05 10:42:27 +00:00
case 'C':
columnWidth = Integer.parseInt (line.substring (3));
break;
case 'O':
recalculationOrder = line.charAt (3);
break;
case 'R':
recalculation = line.charAt (3);
break;
case 'F':
globalFormat = line.charAt (3);
break;
case 'P':
// System.out.printf ("Skipping [%s]%n", line);
break;
default:
System.out.printf ("Unknown global format [%s]%n", line);
break;
2016-03-11 02:54:31 +00:00
}
}
2016-03-14 22:35:22 +00:00
public String getTextDisplay (boolean debug)
2015-06-01 09:35:51 +00:00
{
StringBuilder text = new StringBuilder ();
2016-03-06 04:41:15 +00:00
String longLine;
if (false)
longLine = "%+++++++++++++++++++++++++++++++++++++++++++++++++++++++"
+ "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
else
longLine = " "
+ " ";
2016-03-11 01:52:22 +00:00
String underline = "---------------------------------------------------------"
+ "-----------------------------------------------------------------";
2015-06-01 09:35:51 +00:00
2016-03-14 22:55:17 +00:00
int lastRow = 0;
2016-03-05 21:53:29 +00:00
int lastColumn = 0;
2015-06-01 09:35:51 +00:00
2016-03-09 10:38:53 +00:00
StringBuilder heading = new StringBuilder (" ");
2017-03-05 10:42:27 +00:00
for (int column = 0; column <= maxColumn; column++)
2016-03-09 10:38:53 +00:00
{
int width = columnWidth;
2016-03-15 20:27:47 +00:00
if (columnWidths.containsKey (column))
width = columnWidths.get (column);
char letter1 = column < 26 ? ' ' : column < 52 ? 'A' : 'B';
char letter2 = (char) ((column % 26) + 'A');
2016-03-09 10:38:53 +00:00
2016-03-14 08:58:54 +00:00
String fmt =
String.format ("%s%s%%%d.%ds", letter1, letter2, (width - 2), (width - 2));
2016-03-09 10:38:53 +00:00
if (width == 1)
2016-03-14 08:58:54 +00:00
heading.append (letter2);
2016-03-10 02:39:23 +00:00
else if (width == 2)
2016-03-14 08:58:54 +00:00
heading.append (String.format ("%s%s", letter1, letter2));
2016-03-09 10:38:53 +00:00
else
2016-03-11 01:52:22 +00:00
heading.append (String.format (fmt, underline));
2016-03-09 10:38:53 +00:00
}
2016-03-14 22:35:22 +00:00
2017-03-05 10:42:27 +00:00
text.append (String.format ("Global format : %s%n", globalFormat));
text.append (String.format ("Column width : %d%n", columnWidth));
text.append (String.format ("Recalculation order : %s%n",
recalculationOrder == 'R' ? "Row" : "Column"));
text.append (String.format ("Recalculation : %s%n",
recalculation == 'A' ? "Automatic" : "Manual"));
2017-03-08 09:18:59 +00:00
text.append (String.format ("Cells : %d%n", size ()));
2017-03-05 10:42:27 +00:00
2017-03-08 09:18:59 +00:00
if (size () > 0)
2017-03-05 10:42:27 +00:00
text.append (String.format ("Range : %s:%s%n%n",
Address.getCellName (minRow + 1, minColumn),
Address.getCellName (maxRow + 1, maxColumn)));
else
text.append ("\n\n");
2016-03-14 22:35:22 +00:00
if (debug)
2016-03-14 22:55:17 +00:00
{
2016-03-14 22:35:22 +00:00
text.append (heading);
2016-03-14 22:55:17 +00:00
text.append ("\n001:");
}
2016-03-09 10:38:53 +00:00
2016-03-15 20:06:04 +00:00
for (Cell cell : rowOrderCells.values ())
2015-06-01 09:35:51 +00:00
{
2017-02-25 03:56:22 +00:00
Address cellAddress = cell.getAddress ();
2017-02-26 10:44:10 +00:00
// insert newlines for empty rows
2017-03-03 23:41:08 +00:00
while (lastRow < cellAddress.getRow ())
2015-06-01 09:35:51 +00:00
{
++lastRow;
2016-03-05 21:53:29 +00:00
lastColumn = 0;
2016-03-14 22:35:22 +00:00
if (debug)
text.append (String.format ("%n%03d:", lastRow + 1));
else
text.append ("\n");
2015-06-01 09:35:51 +00:00
}
2017-02-26 10:44:10 +00:00
// pad out empty columns
2017-03-03 23:41:08 +00:00
while (lastColumn < cellAddress.getColumn ())
2015-06-01 09:35:51 +00:00
{
2016-02-04 00:31:44 +00:00
int width = columnWidth;
2016-03-05 21:53:29 +00:00
if (columnWidths.containsKey (lastColumn))
width = columnWidths.get (lastColumn);
2016-02-04 00:31:44 +00:00
text.append (longLine.substring (0, width));
2015-06-01 09:35:51 +00:00
++lastColumn;
}
2016-03-11 22:32:19 +00:00
++lastColumn;
2016-02-04 00:31:44 +00:00
int colWidth = columnWidth;
2017-03-03 23:41:08 +00:00
if (columnWidths.containsKey (cellAddress.getColumn ()))
colWidth = columnWidths.get (cellAddress.getColumn ());
2016-03-05 21:53:29 +00:00
2017-02-27 09:41:05 +00:00
text.append (cell.getText (colWidth, globalFormat));
2015-06-01 09:35:51 +00:00
}
2017-02-25 03:56:22 +00:00
if (debug)
{
text.append ("\n\n");
int last = -1;
for (Cell cell : columnOrderCells.values ())
{
2017-03-03 23:41:08 +00:00
if (last < cell.getAddress ().getColumn ())
2017-02-25 03:56:22 +00:00
{
2017-03-03 23:41:08 +00:00
String columnName = Address.getCellName (1, cell.getAddress ().getColumn ());
2017-02-26 10:44:10 +00:00
columnName = columnName.substring (0, columnName.length () - 1);
text.append ("\n *** Column " + columnName
+ " ***\n\n");
2017-03-03 23:41:08 +00:00
last = cell.getAddress ().getColumn ();
2017-02-25 03:56:22 +00:00
}
text.append (cell.getDebugText ());
text.append ("\n");
}
2017-03-05 10:42:27 +00:00
text.append ("\n");
for (String line : lines)
{
text.append (line);
text.append ("\n");
}
if (text.length () > 0)
text.deleteCharAt (text.length () - 1);
2017-02-25 03:56:22 +00:00
}
2015-06-01 09:35:51 +00:00
return text.toString ();
}
}