/*
* Copyright, Aspect Security, Inc.
*
* This file is part of JavaSnoop.
*
* JavaSnoop is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JavaSnoop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JavaSnoop. If not, see <http://www.gnu.org/licenses/>.
*/
package com.aspect.snoop.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.StyleConstants;
/**
* The TextConsole component. An extension of the
* JTextPane class for user interaction in text
* form.
* @author René Ghosh
* 6 oct. 2007
*/
public class JTextConsole extends JTextPane {
/**
* Default parameters
for basic font name and size.
*/
private static final int DEFAULT_FONT_SIZE = 20;
private static final String DEFAULT_FONT_NAME = "Courier New";
private static final int DEFAULT_WIDTH_CHARS = 80;
private static final int DEFAULT_HEIGHT_CHARS = 25;
private Font font = null;
/**
* flag to set to true when the form is submitted
*/
private volatile boolean finished = false;
MutableAttributeSet attrs = getInputAttributes();
int widthChars = DEFAULT_WIDTH_CHARS;
int heightChars = DEFAULT_HEIGHT_CHARS;
/**
* Maximum numbr of characters that will hold
* in the console window == width * height
*/
int maxLength = pointToInt(widthChars, heightChars);
/**
* The list of keys that shoould be processed
* by the JTextPane superclass.
*/
int[] processable = new int[] {
KeyEvent.VK_UP,
KeyEvent.VK_DOWN,
KeyEvent.VK_LEFT,
KeyEvent.VK_RIGHT,
KeyEvent.VK_HOME,
KeyEvent.VK_END };
/**
* List of zones that support user input.
*/
private List formRanges = new ArrayList();
/**
* Basic constructor: uses default values for
* font size (20) and name (Courier New). Input parameters include
* width and height.
*/
public JTextConsole() {
}
public JTextConsole(int width, int height) {
this(width, height, DEFAULT_FONT_SIZE, DEFAULT_FONT_NAME);
}
/**
* Full constructor taking as input the width and height in number
* of characters, font size and font name.
*/
public JTextConsole(int width, int height, int fontSize, String fontName) {
font = new Font(fontName, Font.BOLD, fontSize);
widthChars = width;
heightChars = height;
setFont(font);
FontRenderContext fontRenderContext = new FontRenderContext(null, true,
true);
Rectangle2D stringBounds = font.getStringBounds(new char[] { 'l' }, 0,
1, fontRenderContext);
setPreferredSize(new Dimension(
(int) ((widthChars + 1) * stringBounds.getWidth()),
(int) ((heightChars + 1.5) * stringBounds.getHeight())));
setForeground(Color.WHITE);
setBackground(Color.BLACK);
setCaretColor(Color.WHITE);
fill();
}
/**
* Rendering method. Overrides the paint() method
* on the superclass to add antialiasing to the output
* screen.
*/
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
super.paint(g);
}
/**
* Method to fill the screen with blank characters (space)
*/
private void fill() {
StringBuffer buffer = new StringBuffer();
for (int j = 0; j < heightChars; j++) {
for (int i = 0; i < widthChars; i++) {
buffer.append(" ");
}
if (j < heightChars - 1) {
buffer.append("\n");
}
}
setText(buffer.toString());
}
/**
* Convert an X-Y position into a sequential index
* of a character in the text pane.
*/
private int pointToInt(int i, int j) {
int ret = ((j - 1) * (widthChars + 1)) + (i - 1);
return ret;
}
/**
* set the cursor position on screen
*/
public void gotoPosition(int x, int y) {
int start = pointToInt(x, y);
setCaretPosition(start);
}
/**
* set the cursor position on the first
* form field on screen
*/
public void gotoFirstField() {
if (formRanges.size() > 0) {
FormRange firstRange = (FormRange) formRanges.get(0);
setCaretPosition(firstRange.start);
}
}
/**
* Clear the screen
*/
public void clear() {
setForeground(Color.WHITE);
fill();
reset();
}
/**
* set the prescribed color to all characters in a given range
*/
public void color(int i, int j, Color color) {
StyleConstants.setForeground(attrs, color);
getStyledDocument().setCharacterAttributes(i, j - i,
attrs, true);
}
/**
* Key processing function. Certain key events
* are delegated to the superclass. Others submit
* the user input to the calling object or react to the input
* internally.
*/
protected void processKeyEvent(KeyEvent e) {
char keyChar = e.getKeyChar();
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_ENTER) {
finished = true;
}
boolean needProcess = false;
for (int i = 0; i < processable.length; i++) {
if (processable[i] == keyCode) {
needProcess = true;
}
}
if (!needProcess) {
int caretPosition = getCaretPosition();
for (Iterator iter = formRanges.iterator(); iter.hasNext();) {
FormRange range = (FormRange) iter.next();
int checkPosition = caretPosition;
if (keyCode == KeyEvent.VK_BACK_SPACE) {
checkPosition = caretPosition - 1;
}
if (range.isInRange(checkPosition)) {
if (e.getID() == KeyEvent.KEY_PRESSED) {
setLocalColor(range.color);
if (Character.isLetterOrDigit(keyChar)) {
write("" + keyChar);
} else if (keyCode == KeyEvent.VK_SPACE) {
write(" ");
} else if (keyCode == KeyEvent.VK_DELETE) {
write(" ");
} else if (keyCode == KeyEvent.VK_BACK_SPACE) {
setCaretPosition(getCaretPosition() - 1);
write(" ");
setCaretPosition(getCaretPosition() - 1);
}
}
}
}
if ((keyCode == KeyEvent.VK_TAB)
&& (e.getID() == KeyEvent.KEY_PRESSED)) {
boolean found = false;
if (e.isShiftDown()) {
Collections.reverse(formRanges);
}
for (Iterator iter = formRanges.iterator(); iter.hasNext()
&& (!found);) {
FormRange hotRange = (FormRange) iter.next();
int start = hotRange.start;
if (e.isShiftDown()) {
if (start < caretPosition) {
setCaretPosition(start);
found = true;
}
} else {
if (start > caretPosition) {
setCaretPosition(start);
found = true;
}
}
}
if (e.isShiftDown()) {
Collections.sort(formRanges);
}
if (!found && (formRanges.size() > 0)) {
setCaretPosition(((FormRange) formRanges.get(0)).start);
}
}
}
if (needProcess) {
super.processKeyEvent(e);
}
}
/**
* Set the foreground font color to that
* of the foreground text under the cursor
*/
private void setLocalColor(Color color) {
setForeground(color);
}
/**
* reset the "finished" flag to false. This flag remains false
* so long as the user has not submitted the form and blocks the getValues()
* method from returning.
*/
public void reset() {
finished = false;
}
/**
* Write text to screen with current foregound color
*/
public void write(String string) {
int caretPosition = getCaretPosition();
if (caretPosition + string.length() > maxLength) {
string = string.substring(0, maxLength - caretPosition + 1);
}
int start = caretPosition;
int end = caretPosition + string.length();
setSelectionStart(start);
setSelectionEnd(end);
replaceSelection(string);
setSelectionStart(getCaretPosition());
color(start, end, getForeground());
}
/**
* Set the foreground to prescribed color and
* write text to screen
*/
public void write(String string, Color color) {
setForeground(color);
write(string);
}
/**
* Move cursor to prescribed position and write
* text to screen
*/
public void write(String string, int x, int y) {
gotoPosition(x, y);
write(string);
}
/**
* Move cursor to prescribed position, set foreground color
* to prescribed color and write text to screen
*/
public void write(String string, int x, int y, Color color) {
setForeground(color);
gotoPosition(x, y);
write(string);
}
/**
* Return the values map to the calling object.
* Each value in the map is associated to a key added
* during the call to addFormField().
*/
public Map getValues() {
while (!finished) {
//do nothing until not finished
}
Map map = new HashMap();
for (Iterator iter = formRanges.iterator(); iter.hasNext();) {
FormRange range = (FormRange) iter.next();
try {
String text = getText(range.start, range.end - range.start);
map.put(range.name, text.trim());
} catch (BadLocationException e) {
e.printStackTrace();
}
}
return map;
}
/**
* Add a form field to the screen with prescribed
* name key and in a given width. the getValues()
* method will return the value associated to this key in
* the input map.
*/
public void addFormField(String fieldName, int width) {
int start = getCaretPosition();
int end = getCaretPosition() + width;
addFormRange(fieldName, start, end);
}
/**
* Add a form field in a prescribed Color.
* @see addFormField()
*/
public void addFormField(String fieldName, int width, Color color) {
setForeground(color);
addFormField(fieldName, width);
}
/**
* Move the cursor to a prescribed position on the screen and
* add a form field.
* @see addFormField()
*/
public void addFormField(String fieldName, int x, int y, int width) {
int start = pointToInt(x, y);
int end = pointToInt(x + width, y);
addFormRange(fieldName, start, end);
}
/**
* Move the cursor to a prescribed position on the screen,
* set the foreground color to the prescribed color and
* add a form field.
* @see addFormField()
*/
public void addFormField(String fieldName, int x, int y, int width,
Color color) {
setForeground(color);
int start = pointToInt(x, y);
int end = pointToInt(x + width, y);
addFormRange(fieldName, start, end);
}
/**
* Add a range to the list of form ranges, from the prescribed
* start parameter to the end parameter
*/
private void addFormRange(String fieldName, int start, int end) {
FormRange range = new FormRange(fieldName, start, end, getForeground());
formRanges.add(range);
Collections.sort(formRanges);
StyleConstants.setUnderline(attrs, true);
getStyledDocument().setCharacterAttributes(start, end - start,attrs, true);
color(start, end, getForeground());
}
/**
* Form range container class.
* Contains information about a form range, including
* - form field key name
* - start of range
* - end of range
* - color of range
* @author René Ghosh
* 6 oct. 2007
*/
class FormRange implements Comparable {
private String name;
private int start;
private int end;
private Color color;
/**
* Constructor using name, start, end and color
* to set into the object
*/
public FormRange(String name, int start, int end, Color color) {
this.name = name;
this.start = start;
this.end = end;
this.color = color;
}
/**
* Returns "true" if the given int is in the
* [start, end] range
*/
public boolean isInRange(int i) {
return (i >= start) && (i < end);
}
/**
* Method to enable sorting on a list of ranges.
*/
public int compareTo(Object other) {
FormRange otherRange = (FormRange) other;
return new Integer(start).compareTo(new Integer(otherRange.start));
}
}
}