*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.swing.JComponent;
* and uses a SwingComponent wrapper class to call the JFrame or JComponent
* methods.
*/
-public final class SwingTerminal extends LogicalScreen
- implements TerminalReader,
- ComponentListener, KeyListener,
- MouseListener, MouseMotionListener,
- MouseWheelListener, WindowListener {
+public class SwingTerminal extends LogicalScreen
+ implements TerminalReader,
+ ComponentListener, KeyListener,
+ MouseListener, MouseMotionListener,
+ MouseWheelListener, WindowListener {
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------
/**
* The terminus font resource filename.
*/
- private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
+ public static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
/**
* Cursor style to draw.
*/
private Map<Cell, BufferedImage> glyphCache;
- /**
- * If true, we were successful getting Terminus.
- */
- private boolean gotTerminus = false;
-
/**
* If true, we were successful at getting the font dimensions.
*/
*/
private int textHeight = 1;
+ /**
+ * Width of a character cell in pixels, as reported by font.
+ */
+ private int fontTextWidth = 1;
+
+ /**
+ * Height of a character cell in pixels, as reported by font.
+ */
+ private int fontTextHeight = 1;
+
/**
* Descent of a character cell in pixels.
*/
*/
private int textAdjustX = 0;
+ /**
+ * System-dependent height adjustment for text in the character cell.
+ */
+ private int textAdjustHeight = 0;
+
+ /**
+ * System-dependent width adjustment for text in the character cell.
+ */
+ private int textAdjustWidth = 0;
+
/**
* Top pixel absolute location.
*/
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Static constructor.
+ */
+ static {
+ setDOSColors();
+ }
+
/**
* Public constructor creates a new JFrame to render to.
*
this.fontSize = fontSize;
- setDOSColors();
-
- // Figure out my cursor style.
- String cursorStyleString = System.getProperty(
- "jexer.Swing.cursorStyle", "underline").toLowerCase();
- if (cursorStyleString.equals("underline")) {
- cursorStyle = CursorStyle.UNDERLINE;
- } else if (cursorStyleString.equals("outline")) {
- cursorStyle = CursorStyle.OUTLINE;
- } else if (cursorStyleString.equals("block")) {
- cursorStyle = CursorStyle.BLOCK;
- }
-
- // Pull the system property for triple buffering.
- if (System.getProperty("jexer.Swing.tripleBuffer") != null) {
- if (System.getProperty("jexer.Swing.tripleBuffer").equals("true")) {
- SwingComponent.tripleBuffer = true;
- } else {
- SwingComponent.tripleBuffer = false;
- }
- }
+ reloadOptions();
try {
SwingUtilities.invokeAndWait(new Runnable() {
SwingTerminal.this.top = insets.top;
// Load the font so that we can set sessionInfo.
- getDefaultFont();
+ setDefaultFont();
// Get the default cols x rows and set component size
// accordingly.
SwingTerminal.this.textHeight,
windowWidth, windowHeight);
- SwingTerminal.this.setDimensions(sessionInfo.getWindowWidth(),
- sessionInfo.getWindowHeight());
+ SwingTerminal.this.setDimensions(sessionInfo.
+ getWindowWidth(), sessionInfo.getWindowHeight());
SwingTerminal.this.resizeToScreen();
SwingTerminal.this.swing.setVisible(true);
}
});
- } catch (Exception e) {
+ } catch (java.lang.reflect.InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
e.printStackTrace();
}
mouse1 = false;
mouse2 = false;
mouse3 = false;
- eventQueue = new LinkedList<TInputEvent>();
+ eventQueue = new ArrayList<TInputEvent>();
// Add listeners to Swing.
swing.addKeyListener(this);
this.fontSize = fontSize;
- setDOSColors();
-
- // Figure out my cursor style.
- String cursorStyleString = System.getProperty(
- "jexer.Swing.cursorStyle", "underline").toLowerCase();
- if (cursorStyleString.equals("underline")) {
- cursorStyle = CursorStyle.UNDERLINE;
- } else if (cursorStyleString.equals("outline")) {
- cursorStyle = CursorStyle.OUTLINE;
- } else if (cursorStyleString.equals("block")) {
- cursorStyle = CursorStyle.BLOCK;
- }
+ reloadOptions();
try {
SwingUtilities.invokeAndWait(new Runnable() {
SwingTerminal.this.top = insets.top;
// Load the font so that we can set sessionInfo.
- getDefaultFont();
+ setDefaultFont();
// Get the default cols x rows and set component size
// accordingly.
SwingTerminal.this.textHeight);
}
});
- } catch (Exception e) {
+ } catch (java.lang.reflect.InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
e.printStackTrace();
}
mouse1 = false;
mouse2 = false;
mouse3 = false;
- eventQueue = new LinkedList<TInputEvent>();
+ eventQueue = new ArrayList<TInputEvent>();
// Add listeners to Swing.
swing.addKeyListener(this);
&& (swing.getBufferStrategy() != null)
) {
do {
+ clearPhysical();
do {
drawToSwing();
} while (swing.getBufferStrategy().contentsRestored());
this.listener = listener;
}
+ /**
+ * Reload options from System properties.
+ */
+ public void reloadOptions() {
+ // Figure out my cursor style.
+ String cursorStyleString = System.getProperty(
+ "jexer.Swing.cursorStyle", "underline").toLowerCase();
+ if (cursorStyleString.equals("underline")) {
+ cursorStyle = CursorStyle.UNDERLINE;
+ } else if (cursorStyleString.equals("outline")) {
+ cursorStyle = CursorStyle.OUTLINE;
+ } else if (cursorStyleString.equals("block")) {
+ cursorStyle = CursorStyle.BLOCK;
+ }
+
+ // Pull the system property for triple buffering.
+ if (System.getProperty("jexer.Swing.tripleBuffer",
+ "true").equals("true")
+ ) {
+ SwingComponent.tripleBuffer = true;
+ } else {
+ SwingComponent.tripleBuffer = false;
+ }
+ }
+
// ------------------------------------------------------------------------
// SwingTerminal ----------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Get the width of a character cell in pixels.
+ *
+ * @return the width in pixels of a character cell
+ */
+ public int getTextWidth() {
+ return textWidth;
+ }
+
+ /**
+ * Get the height of a character cell in pixels.
+ *
+ * @return the height in pixels of a character cell
+ */
+ public int getTextHeight() {
+ return textHeight;
+ }
+
/**
* Setup Swing colors to match DOS color palette.
*/
return blinkMillis;
}
+ /**
+ * Get the current status of the blink flag.
+ *
+ * @return true if the cursor and blinking text should be visible
+ */
+ public boolean getCursorBlinkVisible() {
+ return cursorBlinkVisible;
+ }
+
/**
* Get the font size in points.
*
* @param font the new font
*/
public void setFont(final Font font) {
- this.font = font;
- getFontDimensions();
- swing.setFont(font);
- glyphCacheBlink = new HashMap<Cell, BufferedImage>();
- glyphCache = new HashMap<Cell, BufferedImage>();
- resizeToScreen();
+ synchronized (this) {
+ this.font = font;
+ getFontDimensions();
+ swing.setFont(font);
+ glyphCacheBlink = new HashMap<Cell, BufferedImage>();
+ glyphCache = new HashMap<Cell, BufferedImage>();
+ resizeToScreen();
+ }
+ }
+
+ /**
+ * Get the font this screen was last set to.
+ *
+ * @return the font
+ */
+ public Font getFont() {
+ return font;
}
/**
* Set the font to Terminus, the best all-around font for both CP437 and
* ISO8859-1.
*/
- public void getDefaultFont() {
+ public void setDefaultFont() {
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream in = loader.getResourceAsStream(FONTFILE);
Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
Font terminus = terminusRoot.deriveFont(Font.PLAIN, fontSize);
- gotTerminus = true;
font = terminus;
- } catch (Exception e) {
+ } catch (java.awt.FontFormatException e) {
+ e.printStackTrace();
+ font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
+ } catch (java.io.IOException e) {
e.printStackTrace();
font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
}
setFont(font);
}
+ /**
+ * Get the X text adjustment.
+ *
+ * @return X text adjustment
+ */
+ public int getTextAdjustX() {
+ return textAdjustX;
+ }
+
+ /**
+ * Set the X text adjustment.
+ *
+ * @param textAdjustX the X text adjustment
+ */
+ public void setTextAdjustX(final int textAdjustX) {
+ synchronized (this) {
+ this.textAdjustX = textAdjustX;
+ glyphCacheBlink = new HashMap<Cell, BufferedImage>();
+ glyphCache = new HashMap<Cell, BufferedImage>();
+ clearPhysical();
+ }
+ }
+
+ /**
+ * Get the Y text adjustment.
+ *
+ * @return Y text adjustment
+ */
+ public int getTextAdjustY() {
+ return textAdjustY;
+ }
+
+ /**
+ * Set the Y text adjustment.
+ *
+ * @param textAdjustY the Y text adjustment
+ */
+ public void setTextAdjustY(final int textAdjustY) {
+ synchronized (this) {
+ this.textAdjustY = textAdjustY;
+ glyphCacheBlink = new HashMap<Cell, BufferedImage>();
+ glyphCache = new HashMap<Cell, BufferedImage>();
+ clearPhysical();
+ }
+ }
+
+ /**
+ * Get the height text adjustment.
+ *
+ * @return height text adjustment
+ */
+ public int getTextAdjustHeight() {
+ return textAdjustHeight;
+ }
+
+ /**
+ * Set the height text adjustment.
+ *
+ * @param textAdjustHeight the height text adjustment
+ */
+ public void setTextAdjustHeight(final int textAdjustHeight) {
+ synchronized (this) {
+ this.textAdjustHeight = textAdjustHeight;
+ textHeight = fontTextHeight + textAdjustHeight;
+ glyphCacheBlink = new HashMap<Cell, BufferedImage>();
+ glyphCache = new HashMap<Cell, BufferedImage>();
+ clearPhysical();
+ }
+ }
+
+ /**
+ * Get the width text adjustment.
+ *
+ * @return width text adjustment
+ */
+ public int getTextAdjustWidth() {
+ return textAdjustWidth;
+ }
+
+ /**
+ * Set the width text adjustment.
+ *
+ * @param textAdjustWidth the width text adjustment
+ */
+ public void setTextAdjustWidth(final int textAdjustWidth) {
+ synchronized (this) {
+ this.textAdjustWidth = textAdjustWidth;
+ textWidth = fontTextWidth + textAdjustWidth;
+ glyphCacheBlink = new HashMap<Cell, BufferedImage>();
+ glyphCache = new HashMap<Cell, BufferedImage>();
+ clearPhysical();
+ }
+ }
+
/**
* Convert a CellAttributes foreground color to an Swing Color.
*
* @param attr the text attributes
* @return the Swing Color
*/
- private Color attrToForegroundColor(final CellAttributes attr) {
+ public static Color attrToForegroundColor(final CellAttributes attr) {
+ int rgb = attr.getForeColorRGB();
+ if (rgb >= 0) {
+ int red = (rgb >> 16) & 0xFF;
+ int green = (rgb >> 8) & 0xFF;
+ int blue = rgb & 0xFF;
+
+ return new Color(red, green, blue);
+ }
+
if (attr.isBold()) {
if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
return MYBOLD_BLACK;
* @param attr the text attributes
* @return the Swing Color
*/
- private Color attrToBackgroundColor(final CellAttributes attr) {
+ public static Color attrToBackgroundColor(final CellAttributes attr) {
+ int rgb = attr.getBackColorRGB();
+ if (rgb >= 0) {
+ int red = (rgb >> 16) & 0xFF;
+ int green = (rgb >> 8) & 0xFF;
+ int blue = rgb & 0xFF;
+
+ return new Color(red, green, blue);
+ }
+
if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
return MYBLACK;
} else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
}
/**
- * Figure out what textAdjustX and textAdjustY should be, based on the
- * location of a vertical bar (to find textAdjustY) and a horizontal bar
- * (to find textAdjustX).
- *
- * @return true if textAdjustX and textAdjustY were guessed at correctly
+ * Figure out what textAdjustX, textAdjustY, textAdjustHeight, and
+ * textAdjustWidth should be, based on the location of a vertical bar and
+ * a horizontal bar.
*/
- private boolean getFontAdjustments() {
+ private void getFontAdjustments() {
BufferedImage image = null;
// What SHOULD happen is that the topmost/leftmost white pixel is at
Graphics2D gr2 = null;
int gr2x = 3;
int gr2y = 3;
- image = new BufferedImage(textWidth * 2, textHeight * 2,
+ image = new BufferedImage(fontTextWidth * 2, fontTextHeight * 2,
BufferedImage.TYPE_INT_ARGB);
gr2 = image.createGraphics();
gr2.setFont(swing.getFont());
gr2.setColor(java.awt.Color.BLACK);
- gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
+ gr2.fillRect(0, 0, fontTextWidth * 2, fontTextHeight * 2);
gr2.setColor(java.awt.Color.WHITE);
char [] chars = new char[1];
+ chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR;
+ gr2.drawChars(chars, 0, 1, gr2x, gr2y + fontTextHeight - maxDescent);
chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR;
- gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
+ gr2.drawChars(chars, 0, 1, gr2x, gr2y + fontTextHeight - maxDescent);
gr2.dispose();
- for (int x = 0; x < textWidth; x++) {
- for (int y = 0; y < textHeight; y++) {
-
- /*
- System.err.println("X: " + x + " Y: " + y + " " +
- image.getRGB(x, y));
- */
-
- if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
- textAdjustY = (gr2y - y);
-
- // System.err.println("textAdjustY: " + textAdjustY);
- x = textWidth;
- break;
- }
- }
- }
-
- gr2 = image.createGraphics();
- gr2.setFont(swing.getFont());
- gr2.setColor(java.awt.Color.BLACK);
- gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
- gr2.setColor(java.awt.Color.WHITE);
- chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR;
- gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
- gr2.dispose();
+ int top = fontTextHeight * 2;
+ int bottom = -1;
+ int left = fontTextWidth * 2;
+ int right = -1;
+ textAdjustX = 0;
+ textAdjustY = 0;
+ textAdjustHeight = 0;
+ textAdjustWidth = 0;
- for (int x = 0; x < textWidth; x++) {
- for (int y = 0; y < textHeight; y++) {
+ for (int x = 0; x < fontTextWidth * 2; x++) {
+ for (int y = 0; y < fontTextHeight * 2; y++) {
/*
- System.err.println("X: " + x + " Y: " + y + " " +
+ System.err.println("H X: " + x + " Y: " + y + " " +
image.getRGB(x, y));
- */
+ */
if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
- textAdjustX = (gr2x - x);
-
- // System.err.println("textAdjustX: " + textAdjustX);
- return true;
+ // Pixel is present.
+ if (y < top) {
+ top = y;
+ }
+ if (y > bottom) {
+ bottom = y;
+ }
+ if (x < left) {
+ left = x;
+ }
+ if (x > right) {
+ right = x;
+ }
}
}
}
+ if (left < right) {
+ textAdjustX = (gr2x - left);
+ textAdjustWidth = fontTextWidth - (right - left + 1);
+ }
+ if (top < bottom) {
+ textAdjustY = (gr2y - top);
+ textAdjustHeight = fontTextHeight - (bottom - top + 1);
+ }
+ // System.err.println("top " + top + " bottom " + bottom);
+ // System.err.println("left " + left + " right " + right);
- // Something weird happened, don't rely on this function.
- // System.err.println("getFontAdjustments: false");
- return false;
+ // Special case: do not believe fonts that claim to be wider than
+ // they are tall.
+ if (fontTextWidth >= fontTextHeight) {
+ textAdjustX = 0;
+ textAdjustWidth = 0;
+ fontTextWidth = fontTextHeight / 2;
+ }
}
/**
maxDescent = fm.getMaxDescent();
Rectangle2D bounds = fm.getMaxCharBounds(gr);
int leading = fm.getLeading();
- textWidth = (int)Math.round(bounds.getWidth());
- // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
+ fontTextWidth = (int)Math.round(bounds.getWidth());
+ // fontTextHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
// This produces the same number, but works better for ugly
// monospace.
- textHeight = fm.getMaxAscent() + maxDescent - leading;
+ fontTextHeight = fm.getMaxAscent() + maxDescent - leading;
- if (gotTerminus == true) {
- textHeight++;
- }
-
- if (getFontAdjustments() == false) {
- // We were unable to programmatically determine textAdjustX and
- // textAdjustY, so try some guesses based on VM vendor.
- String runtime = System.getProperty("java.runtime.name");
- if ((runtime != null) && (runtime.contains("Java(TM)"))) {
- textAdjustY = -1;
- textAdjustX = 0;
- }
- }
+ getFontAdjustments();
+ textHeight = fontTextHeight + textAdjustHeight;
+ textWidth = fontTextWidth + textAdjustWidth;
if (sessionInfo != null) {
sessionInfo.setTextCellDimensions(textWidth, textHeight);
}
/**
- * Resize to font dimensions.
+ * Resize the physical screen to match the logical screen dimensions.
*/
+ @Override
public void resizeToScreen() {
swing.setDimensions(textWidth * width, textHeight * height);
+ clearPhysical();
+ }
+
+ /**
+ * Draw one cell's image to the screen.
+ *
+ * @param gr the Swing Graphics context
+ * @param cell the Cell to draw
+ * @param xPixel the x-coordinate to render to. 0 means the
+ * left-most pixel column.
+ * @param yPixel the y-coordinate to render to. 0 means the top-most
+ * pixel row.
+ */
+ private void drawImage(final Graphics gr, final Cell cell,
+ final int xPixel, final int yPixel) {
+
+ /*
+ System.err.println("drawImage(): " + xPixel + " " + yPixel +
+ " " + cell);
+ */
+
+ // Draw the background rectangle, then the foreground character.
+ assert (cell.isImage());
+ gr.setColor(cell.getBackground());
+ gr.fillRect(xPixel, yPixel, textWidth, textHeight);
+
+ BufferedImage image = cell.getImage();
+ if (image != null) {
+ if (swing.getFrame() != null) {
+ gr.drawImage(image, xPixel, yPixel, swing.getFrame());
+ } else {
+ gr.drawImage(image, xPixel, yPixel, swing.getComponent());
+ }
+ return;
+ }
}
/**
*/
}
- if ((swing.getBufferStrategy() != null)
+ if ((swing.getFrame() != null)
+ && (swing.getBufferStrategy() != null)
&& (SwingUtilities.isEventDispatchThread())
) {
// System.err.println("paint(), skip first paint on swing thread");
|| reallyCleared
|| (swing.getFrame() == null)) {
- drawGlyph(gr, lCell, xPixel, yPixel);
+ if (lCell.isImage()) {
+ drawImage(gr, lCell, xPixel, yPixel);
+ } else {
+ drawGlyph(gr, lCell, xPixel, yPixel);
+ }
// Physical is always updated
physical[x][y].setTo(lCell);
&& cursorVisible)
|| (lCell.isBlink())
) {
- drawGlyph(gr, lCell, xPixel, yPixel);
+ if (lCell.isImage()) {
+ drawImage(gr, lCell, xPixel, yPixel);
+ } else {
+ drawGlyph(gr, lCell, xPixel, yPixel);
+ }
physical[x][y].setTo(lCell);
}
}
return sessionInfo;
}
+ /**
+ * Getter for the underlying Swing component.
+ *
+ * @return the SwingComponent
+ */
+ public SwingComponent getSwingComponent() {
+ return swing;
+ }
+
// ------------------------------------------------------------------------
// KeyListener ------------------------------------------------------------
// ------------------------------------------------------------------------
* @param event window event received
*/
public void windowClosing(final WindowEvent event) {
- // Drop a cmAbort and walk away
+ // Drop a cmBackendDisconnect and walk away
synchronized (eventQueue) {
- eventQueue.add(new TCommandEvent(cmAbort));
+ eventQueue.add(new TCommandEvent(cmBackendDisconnect));
resetBlinkTimer();
}
if (listener != null) {
return;
}
+ if (sessionInfo == null) {
+ // This is the initial component resize in construction, bail
+ // out.
+ return;
+ }
+
// Drop a new TResizeEvent into the queue
sessionInfo.queryWindowSize();
synchronized (eventQueue) {