ANSI/ASCII picture viewer

This commit is contained in:
Autumn Lamonte 2021-03-08 12:47:19 -06:00
parent 8306a113e9
commit 6422872706
8 changed files with 965 additions and 59 deletions

View file

@ -1166,6 +1166,10 @@ public class TApplication implements Runnable {
openImage();
return true;
}
if (menu.getId() == TMenu.MID_VIEW_ANSI) {
openAnsiFile();
return true;
}
if (menu.getId() == TMenu.MID_SCREEN_OPTIONS) {
new TScreenOptionsWindow(this);
return true;
@ -2083,6 +2087,21 @@ public class TApplication implements Runnable {
}
}
/**
* Handle the Tool | Open ANSI menu item.
*/
private void openAnsiFile() {
try {
String filename = fileOpenBox(".", TFileOpenBox.Type.OPEN);
if (filename != null) {
new TTextPictureWindow(this, filename);
}
} catch (IOException e) {
// Show this exception to the user.
new TExceptionDialog(this, e);
}
}
/**
* Check if application is still running.
*
@ -3635,6 +3654,7 @@ public class TApplication implements Runnable {
TMenu toolMenu = addMenu(i18n.getString("toolMenuTitle"));
toolMenu.addDefaultItem(TMenu.MID_REPAINT);
toolMenu.addDefaultItem(TMenu.MID_VIEW_IMAGE);
toolMenu.addDefaultItem(TMenu.MID_VIEW_ANSI);
toolMenu.addDefaultItem(TMenu.MID_SCREEN_OPTIONS);
TStatusBar toolStatusBar = toolMenu.newStatusBar(i18n.
getString("toolMenuStatus"));

View file

@ -1390,47 +1390,6 @@ public class TTerminalWidget extends TScrollableWidget
}
}
// ------------------------------------------------------------------------
// DisplayListener --------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Called by emulator when fresh data has come in.
*/
public void displayChanged() {
synchronized (emulator) {
setDirty();
}
TApplication app = getApplication();
if (app != null) {
app.postEvent(new TMenuEvent(null, TMenu.MID_REPAINT));
}
}
/**
* Function to call to obtain the display width.
*
* @return the number of columns in the display
*/
public int getDisplayWidth() {
if (ptypipe) {
return getWidth();
}
return 80;
}
/**
* Function to call to obtain the display height.
*
* @return the number of rows in the display
*/
public int getDisplayHeight() {
if (ptypipe) {
return getHeight();
}
return 24;
}
/**
* Get the exit value for the emulator.
*
@ -1514,6 +1473,47 @@ public class TTerminalWidget extends TScrollableWidget
}
}
// ------------------------------------------------------------------------
// DisplayListener --------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Called by emulator when fresh data has come in.
*/
public void displayChanged() {
synchronized (emulator) {
setDirty();
}
TApplication app = getApplication();
if (app != null) {
app.postEvent(new TMenuEvent(null, TMenu.MID_REPAINT));
}
}
/**
* Function to call to obtain the display width.
*
* @return the number of columns in the display
*/
public int getDisplayWidth() {
if (ptypipe) {
return getWidth();
}
return 80;
}
/**
* Function to call to obtain the display height.
*
* @return the number of rows in the display
*/
public int getDisplayHeight() {
if (ptypipe) {
return getHeight();
}
return 24;
}
// ------------------------------------------------------------------------
// EditMenuUser -----------------------------------------------------------
// ------------------------------------------------------------------------

526
src/jexer/TTextPicture.java Normal file
View file

@ -0,0 +1,526 @@
/*
* Jexer - Java Text User Interface
*
* The MIT License (MIT)
*
* Copyright (C) 2021 Autumn Lamonte
*
* 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.
*
* @author Autumn Lamonte [AutumnWalksTheLake@gmail.com] Trans Liberation Now
* @version 1
*/
package jexer;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import jexer.TScrollableWidget;
import jexer.backend.ECMA48Terminal;
import jexer.backend.GlyphMaker;
import jexer.backend.SwingTerminal;
import jexer.bits.Cell;
import jexer.event.TKeypressEvent;
import jexer.event.TMouseEvent;
import jexer.event.TResizeEvent;
import jexer.tterminal.DisplayLine;
import jexer.tterminal.DisplayListener;
import jexer.tterminal.ECMA48;
import static jexer.TKeypress.*;
/**
* TTextPicture displays a color-and-text canvas, also called "ANSI Art" or
* "ASCII Art".
*/
public class TTextPicture extends TScrollableWidget
implements DisplayListener {
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* The terminal containing the display.
*/
private ECMA48 terminal;
/**
* If true, the terminal is not reading and is closed.
*/
private boolean terminalClosed = true;
/**
* Double-height font.
*/
private GlyphMaker doubleFont;
/**
* Last text width value.
*/
private int lastTextWidth = -1;
/**
* Last text height value.
*/
private int lastTextHeight = -1;
/**
* The blink state, used only by ECMA48 backend and when double-width
* chars must be drawn.
*/
private boolean blinkState = true;
/**
* Timer, used only by ECMA48 backend and when double-width chars must be
* drawn.
*/
private TTimer blinkTimer = null;
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Public constructor.
*
* @param parent parent widget
* @param filename the file containing the picture data
* @param x column relative to parent
* @param y row relative to parent
* @param width width of text area
* @param height height of text area
*/
public TTextPicture(final TWidget parent, final String filename,
final int x, final int y, final int width, final int height) {
// Set parent and window
super(parent, x, y, width, height);
try {
terminal = new ECMA48(ECMA48.DeviceType.XTERM,
new FileInputStream(filename), new ByteArrayOutputStream(),
this);
terminalClosed = false;
} catch (FileNotFoundException e) {
// SQUASH
terminal = null;
} catch (UnsupportedEncodingException e) {
// SQUASH
terminal = null;
}
// We will have scrollers for data fields and mouse event handling,
// but do not want to draw it.
vScroller = new TVScroller(null, getWidth(), 0, getHeight());
vScroller.setVisible(false);
setBottomValue(0);
hScroller = new THScroller(null, 0, getHeight() - 1,
Math.max(1, getWidth() - 1));
hScroller.setVisible(false);
}
// ------------------------------------------------------------------------
// Event handlers ---------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Handle window/screen resize events.
*
* @param resize resize event
*/
@Override
public void onResize(final TResizeEvent resize) {
// Let TWidget set my size.
super.onResize(resize);
if (terminal == null) {
return;
}
synchronized (terminal) {
if (resize.getType() == TResizeEvent.Type.WIDGET) {
// Resize the scroll bars
reflowData();
placeScrollbars();
// Get out of scrollback
setVerticalValue(0);
terminal.setWidth(getWidth());
terminal.setHeight(getHeight());
}
} // synchronized (emulator)
}
/**
* Handle keystrokes.
*
* @param keypress keystroke event
*/
@Override
public void onKeypress(final TKeypressEvent keypress) {
// Scrollback up/down/home/end
if (keypress.equals(kbShiftHome)
|| keypress.equals(kbCtrlHome)
|| keypress.equals(kbAltHome)
) {
toTop();
return;
}
if (keypress.equals(kbShiftEnd)
|| keypress.equals(kbCtrlEnd)
|| keypress.equals(kbAltEnd)
) {
toBottom();
return;
}
if (keypress.equals(kbShiftPgUp)
|| keypress.equals(kbCtrlPgUp)
|| keypress.equals(kbAltPgUp)
) {
bigVerticalDecrement();
return;
}
if (keypress.equals(kbShiftPgDn)
|| keypress.equals(kbCtrlPgDn)
|| keypress.equals(kbAltPgDn)
) {
bigVerticalIncrement();
return;
}
super.onKeypress(keypress);
}
/**
* Handle mouse press events.
*
* @param mouse mouse button press event
*/
@Override
public void onMouseDown(final TMouseEvent mouse) {
if (mouse.isMouseWheelUp()) {
verticalDecrement();
return;
}
if (mouse.isMouseWheelDown()) {
verticalIncrement();
return;
}
super.onMouseDown(mouse);
}
// ------------------------------------------------------------------------
// TScrollableWidget ------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Handle widget close.
*/
@Override
public void close() {
if (terminal != null) {
terminal.close();
}
if (blinkTimer != null) {
TApplication app = getApplication();
if (app != null) {
app.removeTimer(blinkTimer);
}
}
}
/**
* Resize scrollbars for a new width/height.
*/
@Override
public void reflowData() {
if (terminal == null) {
return;
}
// Vertical scrollbar
setTopValue(getHeight()
- (terminal.getScrollbackBuffer().size()
+ terminal.getDisplayBuffer().size()));
setVerticalBigChange(getHeight());
}
// ------------------------------------------------------------------------
// DisplayListener --------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Called by emulator when fresh data has come in.
*/
public void displayChanged() {
// Do nothing
}
/**
* Function to call to obtain the display width.
*
* @return the number of columns in the display
*/
public int getDisplayWidth() {
return getWidth();
}
/**
* Function to call to obtain the display height.
*
* @return the number of rows in the display
*/
public int getDisplayHeight() {
return getHeight();
}
// ------------------------------------------------------------------------
// TTextPicture -----------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Draw the text box.
*/
@Override
public void draw() {
if (terminal == null) {
return;
}
// Check to see if the shell has died.
if (!terminalClosed && !terminal.isReading()) {
try {
terminal.close();
terminalClosed = true;
} catch (IllegalThreadStateException e) {
// SQUASH
}
}
int width = 80;
int left = 0;
List<DisplayLine> display = null;
synchronized (terminal) {
// Update the scroll bars
reflowData();
display = terminal.getVisibleDisplay(getHeight(),
-getVerticalValue());
assert (display.size() == getHeight());
width = terminal.getWidth();
left = getHorizontalValue();
} // synchronized (terminal)
int row = 0;
for (DisplayLine line: display) {
int widthMax = width;
if (line.isDoubleWidth()) {
widthMax /= 2;
}
if (widthMax > getWidth()) {
widthMax = getWidth();
}
for (int i = 0; i < widthMax; i++) {
Cell ch = line.charAt(i + left);
if (ch.isImage()) {
putCharXY(i, row, ch);
continue;
}
Cell newCell = new Cell(ch);
boolean reverse = line.isReverseColor() ^ ch.isReverse();
newCell.setReverse(false);
if (reverse) {
if (ch.getForeColorRGB() < 0) {
newCell.setBackColor(ch.getForeColor());
newCell.setBackColorRGB(-1);
} else {
newCell.setBackColorRGB(ch.getForeColorRGB());
}
if (ch.getBackColorRGB() < 0) {
newCell.setForeColor(ch.getBackColor());
newCell.setForeColorRGB(-1);
} else {
newCell.setForeColorRGB(ch.getBackColorRGB());
}
}
if (line.isDoubleWidth()) {
putDoubleWidthCharXY(line, (i * 2), row, newCell);
} else {
putCharXY(i, row, newCell);
}
}
row++;
}
}
/**
* Draw glyphs for a double-width or double-height VT100 cell to two
* screen cells.
*
* @param line the line this VT100 cell is in
* @param x the X position to draw the left half to
* @param y the Y position to draw to
* @param cell the cell to draw
*/
private void putDoubleWidthCharXY(final DisplayLine line, final int x,
final int y, final Cell cell) {
int textWidth = getScreen().getTextWidth();
int textHeight = getScreen().getTextHeight();
boolean cursorBlinkVisible = true;
if (getScreen() instanceof SwingTerminal) {
SwingTerminal terminal = (SwingTerminal) getScreen();
cursorBlinkVisible = terminal.getCursorBlinkVisible();
} else if (getScreen() instanceof ECMA48Terminal) {
ECMA48Terminal terminal = (ECMA48Terminal) getScreen();
if (!terminal.hasSixel()
&& !terminal.hasJexerImages()
&& !terminal.hasIterm2Images()
) {
// The backend does not have images support, draw this as
// text and bail out.
putCharXY(x, y, cell);
putCharXY(x + 1, y, ' ', cell);
return;
}
cursorBlinkVisible = blinkState;
} else {
// We don't know how to dray glyphs to this screen, draw them as
// text and bail out.
putCharXY(x, y, cell);
putCharXY(x + 1, y, ' ', cell);
return;
}
if ((textWidth != lastTextWidth) || (textHeight != lastTextHeight)) {
// Screen size has changed, reset the font.
setupFont(textHeight);
lastTextWidth = textWidth;
lastTextHeight = textHeight;
}
assert (doubleFont != null);
BufferedImage image;
if (line.getDoubleHeight() == 1) {
// Double-height top half: don't draw the underline.
Cell newCell = new Cell(cell);
newCell.setUnderline(false);
image = doubleFont.getImage(newCell, textWidth * 2, textHeight * 2,
cursorBlinkVisible);
} else {
image = doubleFont.getImage(cell, textWidth * 2, textHeight * 2,
cursorBlinkVisible);
}
// Now that we have the double-wide glyph drawn, copy the right
// pieces of it to the cells.
Cell left = new Cell(cell);
Cell right = new Cell(cell);
right.setChar(' ');
BufferedImage leftImage = null;
BufferedImage rightImage = null;
/*
System.err.println("image " + image + " textWidth " + textWidth +
" textHeight " + textHeight);
*/
switch (line.getDoubleHeight()) {
case 1:
// Top half double height
leftImage = image.getSubimage(0, 0, textWidth, textHeight);
rightImage = image.getSubimage(textWidth, 0, textWidth, textHeight);
break;
case 2:
// Bottom half double height
leftImage = image.getSubimage(0, textHeight, textWidth, textHeight);
rightImage = image.getSubimage(textWidth, textHeight,
textWidth, textHeight);
break;
default:
// Either single height double-width, or error fallback
BufferedImage wideImage = new BufferedImage(textWidth * 2,
textHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D grWide = wideImage.createGraphics();
grWide.drawImage(image, 0, 0, wideImage.getWidth(),
wideImage.getHeight(), null);
grWide.dispose();
leftImage = wideImage.getSubimage(0, 0, textWidth, textHeight);
rightImage = wideImage.getSubimage(textWidth, 0, textWidth,
textHeight);
break;
}
left.setImage(leftImage);
right.setImage(rightImage);
// Since we have image data, ditch the character here. Otherwise, a
// drawBoxShadow() over the terminal window will show the characters
// which looks wrong.
left.setChar(' ');
right.setChar(' ');
putCharXY(x, y, left);
putCharXY(x + 1, y, right);
}
/**
* Set up the double-width font.
*
* @param fontSize the size of font to request for the single-width font.
* The double-width font will be 2x this value.
*/
private void setupFont(final int fontSize) {
doubleFont = GlyphMaker.getInstance(fontSize * 2);
// Special case: the ECMA48 backend needs to have a timer to drive
// its blink state.
if (getScreen() instanceof jexer.backend.ECMA48Terminal) {
if (blinkTimer == null) {
// Blink every 500 millis.
long millis = 500;
blinkTimer = getApplication().addTimer(millis, true,
new TAction() {
public void DO() {
blinkState = !blinkState;
TApplication app = getApplication();
if (app != null) {
app.doRepaint();
}
}
}
);
}
}
}
}

View file

@ -0,0 +1,273 @@
/*
* Jexer - Java Text User Interface
*
* The MIT License (MIT)
*
* Copyright (C) 2021 Autumn Lamonte
*
* 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.
*
* @author Autumn Lamonte [AutumnWalksTheLake@gmail.com] Trans Liberation Now
* @version 1
*/
package jexer;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ResourceBundle;
import javax.imageio.ImageIO;
import jexer.event.TKeypressEvent;
import jexer.event.TMouseEvent;
import jexer.event.TResizeEvent;
import static jexer.TKeypress.*;
/**
* TTextPictureWindow shows an ASCII/ANSI art file with scrollbars.
*/
public class TTextPictureWindow extends TScrollableWindow {
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* The number of lines to scroll on mouse wheel up/down.
*/
private static final int wheelScrollSize = 3;
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Hang onto the TTextPicture so I can resize it with the window.
*/
private TTextPicture pictureField;
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Public constructor opens a file.
*
* @param parent the main application
* @param filename the file to open
* @throws IOException if a java.io operation throws
*/
public TTextPictureWindow(final TApplication parent,
final String filename) throws IOException {
this(parent, filename, 0, 0, 82, 27);
}
/**
* Public constructor opens a file.
*
* @param parent the main application
* @param filename the file to open
* @param x column relative to parent
* @param y row relative to parent
* @param width width of window
* @param height height of window
* @throws IOException if a java.io operation throws
*/
public TTextPictureWindow(final TApplication parent, final String filename,
final int x, final int y, final int width,
final int height) throws IOException {
super(parent, filename, x, y, width, height, RESIZABLE);
pictureField = new TTextPicture(this, filename, 0, 0,
getWidth() - 2, getHeight() - 2);
setTitle(filename);
setupAfterTextPicture();
}
/**
* Setup other fields after the picture is created.
*/
private void setupAfterTextPicture() {
if (pictureField.getHeight() < getHeight() - 2) {
setHeight(pictureField.getHeight() + 2);
}
if (pictureField.getWidth() < getWidth() - 2) {
setWidth(pictureField.getWidth() + 2);
}
hScroller = new THScroller(this,
Math.min(Math.max(0, getWidth() - 17), 17),
getHeight() - 2,
getWidth() - Math.min(Math.max(0, getWidth() - 17), 17) - 3);
vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
reflowData();
}
// ------------------------------------------------------------------------
// Event handlers ---------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Handle mouse press events.
*
* @param mouse mouse button press event
*/
@Override
public void onMouseDown(final TMouseEvent mouse) {
super.onMouseDown(mouse);
setVerticalValue(pictureField.getVerticalValue());
}
/**
* Handle mouse release events.
*
* @param mouse mouse button release event
*/
@Override
public void onMouseUp(final TMouseEvent mouse) {
super.onMouseUp(mouse);
if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
// Clicked/dragged on vertical scrollbar
pictureField.setVerticalValue(getVerticalValue());
}
if (mouse.isMouse1() && mouseOnHorizontalScroller(mouse)) {
// Clicked/dragged on horizontal scrollbar
pictureField.setHorizontalValue(getHorizontalValue());
}
}
/**
* Method that subclasses can override to handle mouse movements.
*
* @param mouse mouse motion event
*/
@Override
public void onMouseMotion(final TMouseEvent mouse) {
super.onMouseMotion(mouse);
if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
// Clicked/dragged on vertical scrollbar
pictureField.setVerticalValue(getVerticalValue());
}
if (mouse.isMouse1() && mouseOnHorizontalScroller(mouse)) {
// Clicked/dragged on horizontal scrollbar
pictureField.setHorizontalValue(getHorizontalValue());
}
}
/**
* Handle window/screen resize events.
*
* @param event resize event
*/
@Override
public void onResize(final TResizeEvent event) {
if (event.getType() == TResizeEvent.Type.WIDGET) {
// Resize the picture field
TResizeEvent pictureSize = new TResizeEvent(event.getBackend(),
TResizeEvent.Type.WIDGET, event.getWidth() - 2,
event.getHeight() - 2);
pictureField.onResize(pictureSize);
// Have TScrollableWindow handle the scrollbars
super.onResize(event);
return;
}
// Pass to children instead
for (TWidget widget: getChildren()) {
widget.onResize(event);
}
}
/**
* Handle keystrokes.
*
* @param keypress keystroke event
*/
@Override
public void onKeypress(final TKeypressEvent keypress) {
if (keypress.equals(kbUp)) {
verticalDecrement();
pictureField.setVerticalValue(getVerticalValue());
return;
}
if (keypress.equals(kbDown)) {
verticalIncrement();
pictureField.setVerticalValue(getVerticalValue());
return;
}
if (keypress.equals(kbPgUp)) {
bigVerticalDecrement();
pictureField.setVerticalValue(getVerticalValue());
return;
}
if (keypress.equals(kbPgDn)) {
bigVerticalIncrement();
pictureField.setVerticalValue(getVerticalValue());
return;
}
if (keypress.equals(kbRight)) {
horizontalIncrement();
pictureField.setHorizontalValue(getHorizontalValue());
return;
}
if (keypress.equals(kbLeft)) {
horizontalDecrement();
pictureField.setHorizontalValue(getHorizontalValue());
return;
}
// We did not take it, let the TTextPicture instance see it.
super.onKeypress(keypress);
}
// ------------------------------------------------------------------------
// TScrollableWindow ------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Draw the window.
*/
@Override
public void draw() {
reflowData();
super.draw();
}
/**
* Resize scrollbars for a new width/height.
*/
@Override
public void reflowData() {
pictureField.reflowData();
setTopValue(pictureField.getTopValue());
setBottomValue(pictureField.getBottomValue());
setVerticalBigChange(pictureField.getVerticalBigChange());
setVerticalValue(pictureField.getVerticalValue());
setHorizontalValue(pictureField.getHorizontalValue());
setRightValue(Math.min(80, 80 - pictureField.getWidth()));
}
}

View file

@ -58,6 +58,11 @@ public class TimeoutInputStream extends InputStream {
*/
private volatile boolean cancel = false;
/**
* If true, EOF was encountered.
*/
private boolean eof = false;
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
@ -108,14 +113,26 @@ public class TimeoutInputStream extends InputStream {
@Override
public int read() throws IOException {
if (eof) {
return -1;
}
if (timeoutMillis == 0) {
// Block on the read().
return stream.read();
int rc = stream.read();
if (rc == -1) {
eof = true;
}
return rc;
}
if (stream.available() > 0) {
// A byte is available now, return it.
return stream.read();
int rc = stream.read();
if (rc == -1) {
eof = true;
}
return rc;
}
// We will wait up to timeoutMillis to see if a byte is available.
@ -143,7 +160,11 @@ public class TimeoutInputStream extends InputStream {
if (stream.available() > 0) {
// A byte is available now, return it.
return stream.read();
int rc = stream.read();
if (rc == -1) {
eof = true;
}
return rc;
}
throw new IOException("InputStream claimed a byte was available, but " +
@ -161,16 +182,29 @@ public class TimeoutInputStream extends InputStream {
*/
@Override
public int read(final byte[] b) throws IOException {
if (eof) {
return -1;
}
if (timeoutMillis == 0) {
// Block on the read().
return stream.read(b);
int rc = stream.read(b);
if (rc == -1) {
eof = true;
}
return rc;
}
int remaining = b.length;
if (stream.available() >= remaining) {
// Enough bytes are available now, return them.
return stream.read(b);
int rc = stream.read(b);
if (rc == -1) {
eof = true;
}
return rc;
}
while (remaining > 0) {
@ -179,7 +213,7 @@ public class TimeoutInputStream extends InputStream {
// available. If not, we throw ReadTimeoutException.
long checkTime = System.currentTimeMillis();
while (stream.available() == 0) {
if (remaining > 0) {
if ((remaining > 0) && (remaining != b.length)) {
return (b.length - remaining);
}
@ -211,6 +245,8 @@ public class TimeoutInputStream extends InputStream {
}
int rc = stream.read(b, b.length - remaining, n);
if (rc == -1) {
eof = true;
// This shouldn't happen.
throw new IOException("InputStream claimed bytes were " +
"available, but read() returned -1. What is going " +
@ -242,16 +278,28 @@ public class TimeoutInputStream extends InputStream {
public int read(final byte[] b, final int off,
final int len) throws IOException {
if (eof) {
return -1;
}
if (timeoutMillis == 0) {
// Block on the read().
return stream.read(b, off, len);
int rc = stream.read(b, off, len);
if (rc == -1) {
eof = true;
}
return rc;
}
int remaining = len;
if (stream.available() >= remaining) {
// Enough bytes are available now, return them.
return stream.read(b, off, remaining);
int rc = stream.read(b, off, remaining);
if (rc <= 0) {
eof = true;
}
return rc;
}
while (remaining > 0) {
@ -260,7 +308,7 @@ public class TimeoutInputStream extends InputStream {
// available. If not, we throw ReadTimeoutException.
long checkTime = System.currentTimeMillis();
while (stream.available() == 0) {
if (remaining > 0) {
if ((remaining > 0) && (remaining != len)) {
return (len - remaining);
}
@ -291,7 +339,9 @@ public class TimeoutInputStream extends InputStream {
n = remaining;
}
int rc = stream.read(b, off + len - remaining, n);
if (rc == -1) {
if (rc <= 0) {
eof = true;
// This shouldn't happen.
throw new IOException("InputStream claimed bytes were " +
"available, but read() returned -1. What is going " +
@ -390,4 +440,13 @@ public class TimeoutInputStream extends InputStream {
cancel = true;
}
/**
* Get the underlying stream.
*
* @return the stream
*/
public InputStream getStream() {
return stream;
}
}

View file

@ -62,7 +62,8 @@ public class TMenu extends TWindow {
// Tools menu
public static final int MID_REPAINT = 1;
public static final int MID_VIEW_IMAGE = 2;
public static final int MID_SCREEN_OPTIONS = 3;
public static final int MID_VIEW_ANSI = 3;
public static final int MID_SCREEN_OPTIONS = 4;
// File menu
public static final int MID_NEW = 10;
@ -631,6 +632,10 @@ public class TMenu extends TWindow {
label = i18n.getString("menuViewImage");
break;
case MID_VIEW_ANSI:
label = i18n.getString("menuViewAnsiArt");
break;
case MID_SCREEN_OPTIONS:
label = i18n.getString("menuScreenOptions");
break;

View file

@ -60,5 +60,6 @@ menuTableFileSaveText=Save As &Text...
menuRepaintDesktop=&Repaint desktop
menuViewImage=&Open image...
menuViewAnsiArt=&View ANSI/ASCII...
menuScreenOptions=&Screen options...

View file

@ -37,6 +37,7 @@ import java.io.CharArrayWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
@ -658,9 +659,10 @@ public class ECMA48 implements Runnable {
this.type = type;
if (inputStream instanceof TimeoutInputStream) {
this.inputStream = (TimeoutInputStream)inputStream;
this.inputStream = (TimeoutInputStream) inputStream;
} else {
this.inputStream = new TimeoutInputStream(inputStream, 2000);
this.inputStream = new TimeoutInputStream(inputStream,
((inputStream instanceof FileInputStream) ? 0 : 2000));
}
if (type == DeviceType.XTERM) {
this.input = new InputStreamReader(new BufferedInputStream(
@ -743,7 +745,14 @@ public class ECMA48 implements Runnable {
} catch (InterruptedException e) {
// SQUASH
}
continue;
if (inputStream.getStream() instanceof FileInputStream) {
// Special case: force a read of files in order
// to see the EOF.
} else {
// Go back to waiting.
continue;
}
}
int rc = -1;
@ -756,6 +765,7 @@ public class ECMA48 implements Runnable {
readBuffer.length);
}
} catch (ReadTimeoutException e) {
// System.err.println("ReadTimeoutException");
rc = 0;
}
@ -803,13 +813,14 @@ public class ECMA48 implements Runnable {
}
// System.err.println("end while loop"); System.err.flush();
} catch (IOException e) {
// System.err.println("IOException");
done = true;
// This is an unusual case. We want to see the stack trace,
// but it is related to the spawned process rather than the
// actual UI. We will generate the stack trace, and consume
// it as though it was emitted by the shell.
CharArrayWriter writer= new CharArrayWriter();
CharArrayWriter writer = new CharArrayWriter();
// Send a ST and RIS to clear the emulator state.
try {
writer.write("\033\\\033c");
@ -1243,16 +1254,27 @@ public class ECMA48 implements Runnable {
if (scrollRegionTop >= scrollRegionBottom) {
scrollRegionTop = 0;
}
currentState.cursorY += delta;
savedState.cursorY += delta;
if (currentState.cursorY < 0) {
currentState.cursorY = 0;
}
if (currentState.cursorY >= height) {
currentState.cursorY = height - 1;
}
if (savedState.cursorY < 0) {
savedState.cursorY = 0;
}
if (savedState.cursorY >= height) {
savedState.cursorY = height - 1;
}
while (display.size() < height) {
DisplayLine line = new DisplayLine(currentState.attr);
line.setReverseColor(reverseVideo);
display.add(line);
if (scrollback.size() == 0) {
DisplayLine line = new DisplayLine(currentState.attr);
line.setReverseColor(reverseVideo);
scrollback.add(0, line);
}
display.add(0, scrollback.remove(scrollback.size() - 1));
}
while (display.size() > height) {
appendScrollbackLine(display.remove(0));