package com.googlecode.lanterna.terminal.swing;
import com.googlecode.lanterna.*;
import com.googlecode.lanterna.graphics.TextGraphics;
import com.googlecode.lanterna.input.DefaultKeyDecodingProfile;
import com.googlecode.lanterna.input.InputDecoder;
import com.googlecode.lanterna.input.KeyStroke;
import com.googlecode.lanterna.input.KeyType;
import com.googlecode.lanterna.terminal.IOSafeTerminal;
import com.googlecode.lanterna.terminal.TerminalResizeListener;
import com.googlecode.lanterna.terminal.virtual.DefaultVirtualTerminal;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
abstract class GraphicalTerminalImplementation implements IOSafeTerminal {
private final TerminalEmulatorDeviceConfiguration deviceConfiguration;
private final TerminalEmulatorColorConfiguration colorConfiguration;
private final DefaultVirtualTerminal virtualTerminal;
private final BlockingQueue<KeyStroke> keyQueue;
private final TerminalScrollController scrollController;
private final DirtyCellsLookupTable dirtyCellsLookupTable;
private final String enquiryString;
private boolean cursorIsVisible;
private boolean enableInput;
private Timer blinkTimer;
private boolean hasBlinkingText;
private boolean blinkOn;
private boolean bellOn;
private boolean needFullRedraw;
private TerminalPosition lastDrawnCursorPosition;
private int lastBufferUpdateScrollPosition;
private int lastComponentWidth;
private int lastComponentHeight;
private BufferedImage backbuffer;
private BufferedImage copybuffer;
GraphicalTerminalImplementation(
TerminalSize initialTerminalSize,
TerminalEmulatorDeviceConfiguration deviceConfiguration,
TerminalEmulatorColorConfiguration colorConfiguration,
TerminalScrollController scrollController) {
if(initialTerminalSize == null) {
initialTerminalSize = new TerminalSize(80, 24);
}
this.virtualTerminal = new DefaultVirtualTerminal(initialTerminalSize);
this.keyQueue = new LinkedBlockingQueue<>();
this.deviceConfiguration = deviceConfiguration;
this.colorConfiguration = colorConfiguration;
this.scrollController = scrollController;
this.dirtyCellsLookupTable = new DirtyCellsLookupTable();
this.cursorIsVisible = true;
this.enableInput = false;
this.enquiryString = "TerminalEmulator";
this.lastDrawnCursorPosition = null;
this.lastBufferUpdateScrollPosition = 0;
this.lastComponentHeight = 0;
this.lastComponentWidth = 0;
this.backbuffer = null;
this.copybuffer = null;
this.blinkTimer = null;
this.hasBlinkingText = false;
this.blinkOn = true;
this.needFullRedraw = false;
virtualTerminal.setBacklogSize(deviceConfiguration.getLineBufferScrollbackSize());
}
TerminalEmulatorDeviceConfiguration getDeviceConfiguration() {
return deviceConfiguration;
}
TerminalEmulatorColorConfiguration getColorConfiguration() {
return colorConfiguration;
}
abstract int getFontHeight();
abstract int getFontWidth();
abstract int getHeight();
abstract int getWidth();
abstract Font getFontForCharacter(TextCharacter character);
abstract boolean isTextAntiAliased();
abstract void repaint();
synchronized void onCreated() {
startBlinkTimer();
enableInput = true;
keyQueue.clear();
}
synchronized void onDestroyed() {
stopBlinkTimer();
enableInput = false;
keyQueue.add(new KeyStroke(KeyType.EOF));
}
synchronized void startBlinkTimer() {
if(blinkTimer != null) {
return;
}
blinkTimer = new Timer("LanternaTerminalBlinkTimer", true);
blinkTimer.schedule(new TimerTask() {
@Override
public void run() {
blinkOn = !blinkOn;
if(hasBlinkingText) {
repaint();
}
}
}, deviceConfiguration.getBlinkLengthInMilliSeconds(), deviceConfiguration.getBlinkLengthInMilliSeconds());
}
synchronized void stopBlinkTimer() {
if(blinkTimer == null) {
return;
}
blinkTimer.cancel();
blinkTimer = null;
}
synchronized Dimension getPreferredSize() {
return new Dimension(getFontWidth() * virtualTerminal.getTerminalSize().getColumns(),
getFontHeight() * virtualTerminal.getTerminalSize().getRows());
}
synchronized void paintComponent(Graphics componentGraphics) {
int width = getWidth();
int height = getHeight();
this.scrollController.updateModel(
virtualTerminal.getBufferLineCount() * getFontHeight(),
height);
boolean needToUpdateBackBuffer =
lastBufferUpdateScrollPosition != scrollController.getScrollingOffset() ||
hasBlinkingText ||
needFullRedraw;
if(width != lastComponentWidth || height != lastComponentHeight) {
int columns = width / getFontWidth();
int rows = height / getFontHeight();
TerminalSize terminalSize = virtualTerminal.getTerminalSize().withColumns(columns).withRows(rows);
virtualTerminal.setTerminalSize(terminalSize);
needToUpdateBackBuffer = true;
}
if(needToUpdateBackBuffer) {
updateBackBuffer(scrollController.getScrollingOffset());
}
ensureGraphicBufferHasRightSize();
Rectangle clipBounds = componentGraphics.getClipBounds();
if(clipBounds == null) {
clipBounds = new Rectangle(0, 0, getWidth(), getHeight());
}
componentGraphics.drawImage(
backbuffer,
clipBounds.x,
clipBounds.y,
clipBounds.width,
clipBounds.height,
clipBounds.x,
clipBounds.y,
clipBounds.width,
clipBounds.height,
null);
int leftoverWidth = getWidth() % getFontWidth();
componentGraphics.setColor(Color.BLACK);
if(leftoverWidth > 0) {
componentGraphics.fillRect(getWidth() - leftoverWidth, 0, leftoverWidth, getHeight());
}
this.lastComponentWidth = width;
this.lastComponentHeight = height;
componentGraphics.dispose();
notifyAll();
}
private synchronized void updateBackBuffer(final int scrollOffsetFromTopInPixels) {
final int fontWidth = getFontWidth();
final int fontHeight = getFontHeight();
final TerminalPosition cursorPosition = virtualTerminal.getCursorBufferPosition();
final TerminalSize viewportSize = virtualTerminal.getTerminalSize();
final int firstVisibleRowIndex = scrollOffsetFromTopInPixels / fontHeight;
final int lastVisibleRowIndex = (scrollOffsetFromTopInPixels + getHeight()) / fontHeight;
ensureGraphicBufferHasRightSize();
final Graphics2D backbufferGraphics = backbuffer.createGraphics();
if(isTextAntiAliased()) {
backbufferGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
backbufferGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
final AtomicBoolean foundBlinkingCharacters = new AtomicBoolean(deviceConfiguration.isCursorBlinking());
buildDirtyCellsLookupTable(firstVisibleRowIndex, lastVisibleRowIndex);
if(lastBufferUpdateScrollPosition < scrollOffsetFromTopInPixels) {
int gap = scrollOffsetFromTopInPixels - lastBufferUpdateScrollPosition;
if(gap / fontHeight < viewportSize.getRows()) {
Graphics2D graphics = copybuffer.createGraphics();
graphics.setClip(0, 0, getWidth(), getHeight() - gap);
graphics.drawImage(backbuffer, 0, -gap, null);
graphics.dispose();
backbufferGraphics.drawImage(copybuffer, 0, 0, getWidth(), getHeight(), 0, 0, getWidth(), getHeight(), null);
if(!dirtyCellsLookupTable.isAllDirty()) {
int previousLastVisibleRowIndex = (lastBufferUpdateScrollPosition + getHeight()) / fontHeight;
for(int row = previousLastVisibleRowIndex; row <= lastVisibleRowIndex; row++) {
dirtyCellsLookupTable.setRowDirty(row);
}
}
}
else {
dirtyCellsLookupTable.setAllDirty();
}
}
else if(lastBufferUpdateScrollPosition > scrollOffsetFromTopInPixels) {
int gap = lastBufferUpdateScrollPosition - scrollOffsetFromTopInPixels;
if(gap / fontHeight < viewportSize.getRows()) {
Graphics2D graphics = copybuffer.createGraphics();
graphics.setClip(0, 0, getWidth(), getHeight() - gap);
graphics.drawImage(backbuffer, 0, 0, null);
graphics.dispose();
backbufferGraphics.drawImage(copybuffer, 0, gap, getWidth(), getHeight(), 0, 0, getWidth(), getHeight() - gap, null);
if(!dirtyCellsLookupTable.isAllDirty()) {
int previousFirstVisibleRowIndex = lastBufferUpdateScrollPosition / fontHeight;
for(int row = firstVisibleRowIndex; row <= previousFirstVisibleRowIndex; row++) {
dirtyCellsLookupTable.setRowDirty(row);
}
}
}
else {
dirtyCellsLookupTable.setAllDirty();
}
}
if(lastComponentWidth < getWidth()) {
if(!dirtyCellsLookupTable.isAllDirty()) {
int lastVisibleColumnIndex = getWidth() / fontWidth;
int previousLastVisibleColumnIndex = lastComponentWidth / fontWidth;
for(int column = previousLastVisibleColumnIndex; column <= lastVisibleColumnIndex; column++) {
dirtyCellsLookupTable.setColumnDirty(column);
}
}
}
if(lastComponentHeight < getHeight()) {
if(!dirtyCellsLookupTable.isAllDirty()) {
int previousLastVisibleRowIndex = (scrollOffsetFromTopInPixels + lastComponentHeight) / fontHeight;
for(int row = previousLastVisibleRowIndex; row <= lastVisibleRowIndex; row++) {
dirtyCellsLookupTable.setRowDirty(row);
}
}
}
virtualTerminal.forEachLine(firstVisibleRowIndex, lastVisibleRowIndex, (rowNumber, bufferLine) -> {
for(int column = 0; column < viewportSize.getColumns(); column++) {
TextCharacter textCharacter = bufferLine.getCharacterAt(column);
boolean atCursorLocation = cursorPosition.equals(column, rowNumber);
if(!atCursorLocation &&
cursorPosition.getColumn() == column + 1 &&
cursorPosition.getRow() == rowNumber &&
textCharacter.isDoubleWidth()) {
atCursorLocation = true;
}
boolean isBlinking = textCharacter.getModifiers().contains(SGR.BLINK);
if(isBlinking) {
foundBlinkingCharacters.set(true);
}
if(dirtyCellsLookupTable.isAllDirty() || dirtyCellsLookupTable.isDirty(rowNumber, column) || isBlinking) {
int characterWidth = fontWidth * (textCharacter.isDoubleWidth() ? 2 : 1);
Color foregroundColor = deriveTrueForegroundColor(textCharacter, atCursorLocation);
Color backgroundColor = deriveTrueBackgroundColor(textCharacter, atCursorLocation);
boolean drawCursor = atCursorLocation && (!deviceConfiguration.isCursorBlinking() || blinkOn);
if(bellOn) {
Color temp = foregroundColor;
foregroundColor = backgroundColor;
backgroundColor = temp;
}
drawCharacter(backbufferGraphics,
textCharacter,
column,
rowNumber,
foregroundColor,
backgroundColor,
fontWidth,
fontHeight,
characterWidth,
scrollOffsetFromTopInPixels,
drawCursor);
}
if(textCharacter.isDoubleWidth()) {
column++;
}
}
});
backbufferGraphics.dispose();
this.hasBlinkingText = foundBlinkingCharacters.get();
this.lastDrawnCursorPosition = cursorPosition;
this.lastBufferUpdateScrollPosition = scrollOffsetFromTopInPixels;
this.needFullRedraw = false;
}
private void buildDirtyCellsLookupTable(int firstRowOffset, int lastRowOffset) {
if(virtualTerminal.isWholeBufferDirtyThenReset() || needFullRedraw) {
dirtyCellsLookupTable.setAllDirty();
return;
}
TerminalSize viewportSize = virtualTerminal.getTerminalSize();
TerminalPosition cursorPosition = virtualTerminal.getCursorBufferPosition();
dirtyCellsLookupTable.resetAndInitialize(firstRowOffset, lastRowOffset, viewportSize.getColumns());
dirtyCellsLookupTable.setDirty(cursorPosition);
if(lastDrawnCursorPosition != null && !lastDrawnCursorPosition.equals(cursorPosition)) {
if(virtualTerminal.getCharacter(lastDrawnCursorPosition).isDoubleWidth()) {
dirtyCellsLookupTable.setDirty(lastDrawnCursorPosition.withRelativeColumn(1));
}
if(lastDrawnCursorPosition.getColumn() > 0 && virtualTerminal.getCharacter(lastDrawnCursorPosition.withRelativeColumn(-1)).isDoubleWidth()) {
dirtyCellsLookupTable.setDirty(lastDrawnCursorPosition.withRelativeColumn(-1));
}
dirtyCellsLookupTable.setDirty(lastDrawnCursorPosition);
}
TreeSet<TerminalPosition> dirtyCells = virtualTerminal.getAndResetDirtyCells();
for(TerminalPosition position: dirtyCells) {
dirtyCellsLookupTable.setDirty(position);
}
}
private void ensureGraphicBufferHasRightSize() {
if(backbuffer == null) {
backbuffer = new BufferedImage(getWidth() * 2, getHeight() * 2, BufferedImage.TYPE_INT_RGB);
copybuffer = new BufferedImage(getWidth() * 2, getHeight() * 2, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = backbuffer.createGraphics();
graphics.setColor(colorConfiguration.toAWTColor(TextColor.ANSI.DEFAULT, false, false));
graphics.fillRect(0, 0, getWidth() * 2, getHeight() * 2);
graphics.dispose();
}
if(backbuffer.getWidth() < getWidth() || backbuffer.getWidth() > getWidth() * 4 ||
backbuffer.getHeight() < getHeight() || backbuffer.getHeight() > getHeight() * 4) {
BufferedImage newBackbuffer = new BufferedImage(Math.max(getWidth(), 1) * 2, Math.max(getHeight(), 1) * 2, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = newBackbuffer.createGraphics();
graphics.fillRect(0, 0, newBackbuffer.getWidth(), newBackbuffer.getHeight());
graphics.drawImage(backbuffer, 0, 0, null);
graphics.dispose();
backbuffer = newBackbuffer;
copybuffer = new BufferedImage(Math.max(getWidth(), 1) * 2, Math.max(getHeight(), 1) * 2, BufferedImage.TYPE_INT_RGB);
}
}
private void drawCharacter(
Graphics g,
TextCharacter character,
int columnIndex,
int rowIndex,
Color foregroundColor,
Color backgroundColor,
int fontWidth,
int fontHeight,
int characterWidth,
int scrollingOffsetInPixels,
boolean drawCursor) {
int x = columnIndex * fontWidth;
int y = rowIndex * fontHeight - scrollingOffsetInPixels;
g.setColor(backgroundColor);
g.setClip(x, y, characterWidth, fontHeight);
g.fillRect(x, y, characterWidth, fontHeight);
g.setColor(foregroundColor);
Font font = getFontForCharacter(character);
g.setFont(font);
FontMetrics fontMetrics = g.getFontMetrics();
g.drawString(character.getCharacterString(), x, y + fontHeight - fontMetrics.getDescent() + 1);
if(character.isCrossedOut()) {
int lineStartX = x;
int lineStartY = y + (fontHeight / 2);
int lineEndX = lineStartX + characterWidth;
g.drawLine(lineStartX, lineStartY, lineEndX, lineStartY);
}
if(character.isUnderlined()) {
int lineStartX = x;
int lineStartY = y + fontHeight - fontMetrics.getDescent() + 1;
int lineEndX = lineStartX + characterWidth;
g.drawLine(lineStartX, lineStartY, lineEndX, lineStartY);
}
if(drawCursor) {
if(deviceConfiguration.getCursorColor() == null) {
g.setColor(foregroundColor);
}
else {
g.setColor(colorConfiguration.toAWTColor(deviceConfiguration.getCursorColor(), false, false));
}
if(deviceConfiguration.getCursorStyle() == TerminalEmulatorDeviceConfiguration.CursorStyle.UNDER_BAR) {
g.fillRect(x, y + fontHeight - 3, characterWidth, 2);
}
else if(deviceConfiguration.getCursorStyle() == TerminalEmulatorDeviceConfiguration.CursorStyle.VERTICAL_BAR) {
g.fillRect(x, y + 1, 2, fontHeight - 2);
}
}
}
private Color deriveTrueForegroundColor(TextCharacter character, boolean atCursorLocation) {
TextColor foregroundColor = character.getForegroundColor();
TextColor backgroundColor = character.getBackgroundColor();
boolean reverse = character.isReversed();
boolean blink = character.isBlinking();
if(cursorIsVisible && atCursorLocation) {
if(deviceConfiguration.getCursorStyle() == TerminalEmulatorDeviceConfiguration.CursorStyle.REVERSED &&
(!deviceConfiguration.isCursorBlinking() || !blinkOn)) {
reverse = true;
}
}
if(reverse && (!blink || !blinkOn)) {
return colorConfiguration.toAWTColor(backgroundColor, backgroundColor != TextColor.ANSI.DEFAULT, character.isBold());
}
else if(!reverse && blink && blinkOn) {
return colorConfiguration.toAWTColor(backgroundColor, false, character.isBold());
}
else {
return colorConfiguration.toAWTColor(foregroundColor, true, character.isBold());
}
}
private Color deriveTrueBackgroundColor(TextCharacter character, boolean atCursorLocation) {
TextColor foregroundColor = character.getForegroundColor();
TextColor backgroundColor = character.getBackgroundColor();
boolean reverse = character.isReversed();
if(cursorIsVisible && atCursorLocation) {
if(deviceConfiguration.getCursorStyle() == TerminalEmulatorDeviceConfiguration.CursorStyle.REVERSED &&
(!deviceConfiguration.isCursorBlinking() || !blinkOn)) {
reverse = true;
}
else if(deviceConfiguration.getCursorStyle() == TerminalEmulatorDeviceConfiguration.CursorStyle.FIXED_BACKGROUND) {
backgroundColor = deviceConfiguration.getCursorColor();
}
}
if(reverse) {
return colorConfiguration.toAWTColor(foregroundColor, backgroundColor == TextColor.ANSI.DEFAULT, character.isBold());
}
else {
return colorConfiguration.toAWTColor(backgroundColor, false, false);
}
}
void addInput(KeyStroke keyStroke) {
keyQueue.add(keyStroke);
}
@Override
public KeyStroke pollInput() {
if(!enableInput) {
return new KeyStroke(KeyType.EOF);
}
return keyQueue.poll();
}
@Override
public KeyStroke readInput() {
synchronized(keyQueue) {
if(!enableInput) {
return new KeyStroke(KeyType.EOF);
}
try {
return keyQueue.take();
}
catch(InterruptedException ignore) {
throw new RuntimeException("Blocking input was interrupted");
}
}
}
@Override
public synchronized void enterPrivateMode() {
virtualTerminal.enterPrivateMode();
clearBackBuffer();
flush();
}
@Override
public synchronized void exitPrivateMode() {
virtualTerminal.exitPrivateMode();
clearBackBuffer();
flush();
}
@Override
public synchronized void clearScreen() {
virtualTerminal.clearScreen();
clearBackBuffer();
}
private void clearBackBuffer() {
if(backbuffer != null) {
Graphics2D graphics = backbuffer.createGraphics();
Color backgroundColor = colorConfiguration.toAWTColor(TextColor.ANSI.DEFAULT, false, false);
graphics.setColor(backgroundColor);
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.dispose();
}
}
@Override
public synchronized void setCursorPosition(int x, int y) {
setCursorPosition(new TerminalPosition(x, y));
}
@Override
public synchronized void setCursorPosition(TerminalPosition position) {
if(position.getColumn() < 0) {
position = position.withColumn(0);
}
if(position.getRow() < 0) {
position = position.withRow(0);
}
virtualTerminal.setCursorPosition(position);
}
@Override
public TerminalPosition getCursorPosition() {
return virtualTerminal.getCursorPosition();
}
@Override
public void setCursorVisible(final boolean visible) {
cursorIsVisible = visible;
}
@Override
public synchronized void putCharacter(final char c) {
virtualTerminal.putCharacter(c);
}
@Override
public void putString(String string) {
virtualTerminal.putString(string);
}
@Override
public TextGraphics newTextGraphics() {
return virtualTerminal.newTextGraphics();
}
@Override
public void enableSGR(final SGR sgr) {
virtualTerminal.enableSGR(sgr);
}
@Override
public void disableSGR(final SGR sgr) {
virtualTerminal.disableSGR(sgr);
}
@Override
public void resetColorAndSGR() {
virtualTerminal.resetColorAndSGR();
}
@Override
public void setForegroundColor(final TextColor color) {
virtualTerminal.setForegroundColor(color);
}
@Override
public void setBackgroundColor(final TextColor color) {
virtualTerminal.setBackgroundColor(color);
}
@Override
public synchronized TerminalSize getTerminalSize() {
return virtualTerminal.getTerminalSize();
}
@Override
public byte[] enquireTerminal(int timeout, TimeUnit timeoutUnit) {
return enquiryString.getBytes();
}
@Override
public void bell() {
if(bellOn) {
return;
}
bellOn = true;
needFullRedraw = true;
updateBackBuffer(scrollController.getScrollingOffset());
repaint();
new Thread("BellSilencer") {
@Override
public void run() {
try {
Thread.sleep(100);
}
catch(InterruptedException ignore) {}
bellOn = false;
needFullRedraw = true;
updateBackBuffer(scrollController.getScrollingOffset());
repaint();
}
}.start();
Toolkit.getDefaultToolkit().beep();
}
@Override
public synchronized void flush() {
updateBackBuffer(scrollController.getScrollingOffset());
repaint();
}
@Override
public void close() {
}
@Override
public void addResizeListener(TerminalResizeListener listener) {
virtualTerminal.addResizeListener(listener);
}
@Override
public void removeResizeListener(TerminalResizeListener listener) {
virtualTerminal.removeResizeListener(listener);
}
private static final Set<Character> TYPED_KEYS_TO_IGNORE = new HashSet<>(Arrays.asList('\n', '\t', '\r', '\b', '\33', (char) 127));
protected class TerminalInputListener extends KeyAdapter {
@Override
public void keyTyped(KeyEvent e) {
char character = e.getKeyChar();
boolean altDown = (e.getModifiersEx() & InputEvent.ALT_DOWN_MASK) != 0;
boolean ctrlDown = (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0;
boolean shiftDown = (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0;
if(!TYPED_KEYS_TO_IGNORE.contains(character)) {
if(ctrlDown && character > 0 && character < 0x1a) {
character = (char) ('a' - 1 + character);
if(shiftDown) {
character = Character.toUpperCase(character);
}
}
if(!altDown && ctrlDown && shiftDown && character == 'V' && deviceConfiguration.isClipboardAvailable()) {
pasteClipboardContent();
}
else {
keyQueue.add(new KeyStroke(character, ctrlDown, altDown, shiftDown));
}
}
}
@Override
public void keyPressed(KeyEvent e) {
boolean altDown = (e.getModifiersEx() & InputEvent.ALT_DOWN_MASK) != 0;
boolean ctrlDown = (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0;
boolean shiftDown = (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0;
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
keyQueue.add(new KeyStroke(KeyType.Enter, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_ESCAPE) {
keyQueue.add(new KeyStroke(KeyType.Escape, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
keyQueue.add(new KeyStroke(KeyType.Backspace, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_LEFT) {
keyQueue.add(new KeyStroke(KeyType.ArrowLeft, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
keyQueue.add(new KeyStroke(KeyType.ArrowRight, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_UP) {
keyQueue.add(new KeyStroke(KeyType.ArrowUp, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_DOWN) {
keyQueue.add(new KeyStroke(KeyType.ArrowDown, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_INSERT) {
if(!altDown && !ctrlDown && shiftDown && deviceConfiguration.isClipboardAvailable()) {
pasteClipboardContent();
}
else {
keyQueue.add(new KeyStroke(KeyType.Insert, ctrlDown, altDown, shiftDown));
}
}
else if(e.getKeyCode() == KeyEvent.VK_DELETE) {
keyQueue.add(new KeyStroke(KeyType.Delete, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_HOME) {
keyQueue.add(new KeyStroke(KeyType.Home, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_END) {
keyQueue.add(new KeyStroke(KeyType.End, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_PAGE_UP) {
keyQueue.add(new KeyStroke(KeyType.PageUp, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_PAGE_DOWN) {
keyQueue.add(new KeyStroke(KeyType.PageDown, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F1) {
keyQueue.add(new KeyStroke(KeyType.F1, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F2) {
keyQueue.add(new KeyStroke(KeyType.F2, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F3) {
keyQueue.add(new KeyStroke(KeyType.F3, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F4) {
keyQueue.add(new KeyStroke(KeyType.F4, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F5) {
keyQueue.add(new KeyStroke(KeyType.F5, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F6) {
keyQueue.add(new KeyStroke(KeyType.F6, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F7) {
keyQueue.add(new KeyStroke(KeyType.F7, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F8) {
keyQueue.add(new KeyStroke(KeyType.F8, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F9) {
keyQueue.add(new KeyStroke(KeyType.F9, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F10) {
keyQueue.add(new KeyStroke(KeyType.F10, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F11) {
keyQueue.add(new KeyStroke(KeyType.F11, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_F12) {
keyQueue.add(new KeyStroke(KeyType.F12, ctrlDown, altDown, shiftDown));
}
else if(e.getKeyCode() == KeyEvent.VK_TAB) {
if(e.isShiftDown()) {
keyQueue.add(new KeyStroke(KeyType.ReverseTab, ctrlDown, altDown, false));
}
else {
keyQueue.add(new KeyStroke(KeyType.Tab, ctrlDown, altDown, shiftDown));
}
}
else {
if(altDown && ctrlDown && e.getKeyCode() >= 'A' && e.getKeyCode() <= 'Z') {
char character = (char) e.getKeyCode();
if(!shiftDown) {
character = Character.toLowerCase(character);
}
keyQueue.add(new KeyStroke(character, true, true, shiftDown));
}
}
}
}
protected class TerminalMouseListener extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
if(MouseInfo.getNumberOfButtons() > 2 &&
e.getButton() == MouseEvent.BUTTON2 &&
deviceConfiguration.isClipboardAvailable()) {
pasteSelectionContent();
}
}
}
private void pasteClipboardContent() {
try {
Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
if(systemClipboard != null) {
injectStringAsKeyStrokes((String) systemClipboard.getData(DataFlavor.stringFlavor));
}
}
catch(Exception ignore) {
}
}
private void pasteSelectionContent() {
try {
Clipboard systemSelection = Toolkit.getDefaultToolkit().getSystemSelection();
if(systemSelection != null) {
injectStringAsKeyStrokes((String) systemSelection.getData(DataFlavor.stringFlavor));
}
}
catch(Exception ignore) {
}
}
private void injectStringAsKeyStrokes(String string) {
StringReader stringReader = new StringReader(string);
InputDecoder inputDecoder = new InputDecoder(stringReader);
inputDecoder.addProfile(new DefaultKeyDecodingProfile());
try {
KeyStroke keyStroke = inputDecoder.getNextCharacter(false);
while (keyStroke != null && keyStroke.getKeyType() != KeyType.EOF) {
keyQueue.add(keyStroke);
keyStroke = inputDecoder.getNextCharacter(false);
}
}
catch(IOException ignore) {
}
}
private static class DirtyCellsLookupTable {
private final List<BitSet> table;
private int firstRowIndex;
private boolean allDirty;
DirtyCellsLookupTable() {
table = new ArrayList<>();
firstRowIndex = -1;
allDirty = false;
}
void resetAndInitialize(int firstRowIndex, int lastRowIndex, int columns) {
this.firstRowIndex = firstRowIndex;
this.allDirty = false;
int rows = lastRowIndex - firstRowIndex + 1;
while(table.size() < rows) {
table.add(new BitSet(columns));
}
while(table.size() > rows) {
table.remove(table.size() - 1);
}
for(int index = 0; index < table.size(); index++) {
if(table.get(index).size() != columns) {
table.set(index, new BitSet(columns));
}
else {
table.get(index).clear();
}
}
}
void setAllDirty() {
allDirty = true;
}
boolean isAllDirty() {
return allDirty;
}
void setDirty(TerminalPosition position) {
if(position.getRow() < firstRowIndex ||
position.getRow() >= firstRowIndex + table.size()) {
return;
}
BitSet tableRow = table.get(position.getRow() - firstRowIndex);
if(position.getColumn() < tableRow.size()) {
tableRow.set(position.getColumn());
}
}
void setRowDirty(int rowNumber) {
BitSet row = table.get(rowNumber - firstRowIndex);
row.set(0, row.size());
}
void setColumnDirty(int column) {
for(BitSet row: table) {
if(column < row.size()) {
row.set(column);
}
}
}
boolean isDirty(int row, int column) {
if(row < firstRowIndex || row >= firstRowIndex + table.size()) {
return false;
}
BitSet tableRow = table.get(row - firstRowIndex);
if(column < tableRow.size()) {
return tableRow.get(column);
}
else {
return false;
}
}
}
}