symon/src/main/java/com/loomcom/symon/jterminal/vt100/Vt100TerminalModel.java

494 lines
12 KiB
Java

/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.jterminal.vt100;
import java.awt.Color;
import com.loomcom.symon.jterminal.AbstractTerminalModel;
import com.loomcom.symon.jterminal.TerminalCell;
import com.loomcom.symon.jterminal.TerminalModel;
import com.loomcom.symon.jterminal.bell.BellStrategy;
import com.loomcom.symon.jterminal.bell.NopBellStrategy;
/**
* A VT100/ANSI-compatible terminal model.
* @author Graham Edgecombe
*/
public class Vt100TerminalModel extends AbstractTerminalModel {
/**
* A {@link AnsiControlSequenceListener} which modifies the
* {@link TerminalModel} appropriately when an event happens.
* @author Graham Edgecombe
*/
private class Vt100Listener implements AnsiControlSequenceListener {
/**
* The saved cursor row.
*/
private int savedCursorRow = -1;
/**
* The saved cursor column.
*/
private int savedCursorColumn = -1;
@Override
public void parsedControlSequence(AnsiControlSequence seq) {
char command = seq.getCommand();
String[] parameters = seq.getParameters();
switch (command) {
case 'A':
case 'B':
case 'C':
case 'D':
int n = 1;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
if (command == 'A') {
moveCursorUp(n);
} else if (command == 'B') {
moveCursorDown(n);
} else if (command == 'C') {
moveCursorForward(n);
} else if (command == 'D') {
moveCursorBack(n);
}
break;
case 'E':
case 'F':
n = 1;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
if (command == 'E') {
moveCursorDown(n);
} else if (command == 'F') {
moveCursorUp(n);
}
setCursorColumn(0);
break;
case 'G':
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
setCursorColumn(n - 1);
}
break;
case 'H':
case 'f':
if (parameters.length == 2) {
n = 1;
int m = 1;
if (parameters[0].length() > 0) {
n = Integer.parseInt(parameters[0]);
}
if (parameters[1].length() > 0) {
m = Integer.parseInt(parameters[1]);
}
setCursorRow(n - 1);
setCursorColumn(m - 1);
}
break;
case 'J':
n = 0;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
if (n == 0) {
int row = cursorRow;
int column = cursorColumn;
while(row < rows) {
while(column < columns) {
cells[row][column] = null;
column++;
}
column = 0;
row++;
}
} else if (n == 1) {
int row = cursorRow;
int column = cursorColumn;
while(row >= 0) {
while(column >= 0) {
cells[row][column] = null;
column--;
}
column = columns - 1;
row--;
}
} else if (n == 2) {
clear();
}
break;
case 'K':
n = 0;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
if (n == 0) {
for (int row = cursorRow; row < rows; row++) {
cells[row][cursorColumn] = null;
}
} else if (n == 1) {
for (int row = cursorRow; row >= 0; row--) {
cells[row][cursorColumn] = null;
}
} else if (n == 2) {
for (int column = 0; column < columns; column++) {
cells[cursorRow][column] = null;
}
}
break;
case 'm':
if (parameters.length == 0) {
parameters = new String[] { "0" };
}
for (String parameter : parameters) {
if (parameter.equals("0")) {
foregroundColor = DEFAULT_FOREGROUND_COLOR;
backgroundColor = DEFAULT_BACKGROUND_COLOR;
backgroundBold = DEFAULT_BACKGROUND_BOLD;
foregroundBold = DEFAULT_FOREGROUND_BOLD;
} else if (parameter.equals("2")) {
backgroundBold = true;
foregroundBold = true;
} else if (parameter.equals("22")) {
backgroundBold = false;
foregroundBold = false;
} else if ((parameter.startsWith("3") || parameter.startsWith("4")) && parameter.length() == 2) {
int color = Integer.parseInt(parameter.substring(1));
if (parameter.startsWith("3")) {
foregroundColor = color;
} else if (parameter.startsWith("4")) {
backgroundColor = color;
}
}
}
break;
case 'u':
if (savedCursorColumn != -1 && savedCursorRow != -1) {
cursorColumn = savedCursorColumn;
cursorRow = savedCursorRow;
}
break;
case 's':
savedCursorColumn = cursorColumn;
savedCursorRow = cursorRow;
break;
}
}
@Override
public void parsedString(String str) {
for (char ch : str.toCharArray()) {
switch (ch) {
case '\0':
continue;
case '\r':
cursorColumn = 0;
continue;
case '\n':
cursorRow++;
break;
case '\t':
while ((++cursorColumn % TAB_WIDTH) != 0);
continue;
case 127:
if (cursorColumn > 0) {
cells[cursorRow][--cursorColumn] = null;
}
continue;
case 7:
bellStrategy.soundBell();
continue;
}
if (cursorColumn >= columns) {
cursorColumn = 0;
cursorRow++;
}
if (cursorRow >= bufferSize) {
for (int i = 1; i < bufferSize; i++) {
System.arraycopy(cells[i], 0, cells[i - 1], 0, columns);
}
for (int i = 0; i < columns; i++) {
cells[bufferSize - 1][i] = null;
}
cursorRow--;
}
Color back = backgroundBold ? SgrColor.COLOR_BRIGHT[backgroundColor] : SgrColor.COLOR_NORMAL[backgroundColor];
Color fore = foregroundBold ? SgrColor.COLOR_BRIGHT[foregroundColor] : SgrColor.COLOR_NORMAL[foregroundColor];
if (ch != '\n') {
cells[cursorRow][cursorColumn++] = new TerminalCell(ch, back, fore);
}
}
}
}
/**
* The default number of columns.
*/
private static final int DEFAULT_COLUMNS = 80;
/**
* The default number of rows.
*/
private static final int DEFAULT_ROWS = 25;
/**
* The tab width in characters.
*/
private static final int TAB_WIDTH = 8;
/**
* The default foreground bold flag.
*/
private static final boolean DEFAULT_FOREGROUND_BOLD = false;
/**
* The default background bold flag.
*/
private static final boolean DEFAULT_BACKGROUND_BOLD = false;
/**
* The default foreground color.
*/
private static final int DEFAULT_FOREGROUND_COLOR = 7;
/**
* The default background color.
*/
private static final int DEFAULT_BACKGROUND_COLOR = 0;
/**
* The ANSI control sequence listener.
*/
private final AnsiControlSequenceListener listener = this.new Vt100Listener();
/**
* The ANSI control sequence parser.
*/
private final AnsiControlSequenceParser parser = new AnsiControlSequenceParser(listener);
/**
* The current bell strategy.
*/
private BellStrategy bellStrategy = new NopBellStrategy();
/**
* The array of cells.
*/
private TerminalCell[][] cells;
/**
* The number of columns.
*/
private int columns;
/**
* The number of rows.
*/
private int rows;
/**
* The buffer size.
*/
private int bufferSize;
/**
* The cursor row.
*/
private int cursorRow = 0;
/**
* The cursor column.
*/
private int cursorColumn = 0;
/**
* The current foreground bold flag.
*/
private boolean foregroundBold = DEFAULT_FOREGROUND_BOLD;
/**
* The current background bold flag.
*/
private boolean backgroundBold = DEFAULT_BACKGROUND_BOLD;
/**
* The current foreground color.
*/
private int foregroundColor = DEFAULT_FOREGROUND_COLOR;
/**
* The current background color.
*/
private int backgroundColor = DEFAULT_BACKGROUND_COLOR;
/**
* Creates the terminal model with the default number of columns and rows,
* and the default buffer size.
*/
public Vt100TerminalModel() {
this(DEFAULT_COLUMNS, DEFAULT_ROWS);
}
/**
* Creates the terminal model with the specified number of columns and
* rows. The buffer size is set to the number of rows.
* @param columns The number of columns.
* @param rows The number of rows.
* @throws IllegalArgumentException if the number of rows or columns is
* negative.
*/
public Vt100TerminalModel(int columns, int rows) {
this(columns, rows, rows);
}
/**
* Creates the terminal model with the specified number of columns and rows
* and the specified buffer size.
* @param columns The number of columns.
* @param rows The number of rows.
* @param bufferSize The buffer size.
* @throws IllegalArgumentException if the number of rows or columns is
* negative, or if the buffer size is less than the number of rows.
*/
public Vt100TerminalModel(int columns, int rows, int bufferSize) {
if (columns < 0 || rows < 0 || bufferSize < 0) {
throw new IllegalArgumentException("Zero or positive values only allowed for columns, rows and buffer size.");
}
if (bufferSize < rows) {
throw new IllegalArgumentException("The buffer is too small");
}
this.columns = columns;
this.rows = rows;
this.bufferSize = bufferSize;
init();
}
/**
* Initializes the terminal model.
*/
private void init() {
cells = new TerminalCell[bufferSize][columns];
}
@Override
public int getCursorRow() {
return cursorRow;
}
@Override
public void setCursorRow(int row) {
if (row < 0 || row >= bufferSize) {
throw new IllegalArgumentException("row out of range");
}
cursorRow = row;
}
@Override
public int getCursorColumn() {
return cursorColumn;
}
@Override
public void setCursorColumn(int column) {
if (column < 0 || column >= columns) {
throw new IllegalArgumentException("column out of range");
}
cursorColumn = column;
}
@Override
public TerminalCell getCell(int column, int row) {
if (column < 0 || row < 0 || column >= columns || row >= bufferSize) {
throw new IndexOutOfBoundsException();
}
return cells[row][column];
}
@Override
public void setCell(int column, int row, TerminalCell cell) {
if (column < 0 || row < 0 || column >= columns || row >= bufferSize) {
throw new IndexOutOfBoundsException();
}
cells[row][column] = cell;
}
@Override
public void print(String str) {
if (str == null) {
throw new NullPointerException("str");
}
parser.parse(str);
}
@Override
public int getColumns() {
return columns;
}
@Override
public int getRows() {
return rows;
}
@Override
public int getBufferSize() {
return bufferSize;
}
@Override
public BellStrategy getBellStrategy() {
return bellStrategy;
}
@Override
public void setBellStrategy(BellStrategy strategy) {
if (strategy == null) {
throw new NullPointerException("strategy");
}
this.bellStrategy = strategy;
}
@Override
public Color getDefaultBackgroundColor() {
final int bg = DEFAULT_BACKGROUND_COLOR;
return DEFAULT_BACKGROUND_BOLD ? SgrColor.COLOR_BRIGHT[bg] : SgrColor.COLOR_NORMAL[bg];
}
@Override
public Color getDefaultForegroundColor() {
final int fg = DEFAULT_FOREGROUND_COLOR;
return DEFAULT_FOREGROUND_BOLD ? SgrColor.COLOR_BRIGHT[fg] : SgrColor.COLOR_NORMAL[fg];
}
}