0.0.5
+- Multiscreen support:
+ - cmAbort to cmScreenDisconnected
+ - cmScreenConnected
+
- TApplication
- getAllWindows()
- Expose menu management functions (addMenu, getMenu, getAllMenus,
0.0.7
-- Refactor SwingBackend to be embeddable
- - jexer.Swing.blockMousePointer: false = do not invert cell, true
- (default) is current behavior
- - Make Demo5 with two separate Swing demos in a single JFrame.
- - Make Demo6 mixing Swing and Jexer components
-
- THelpWindow
- TText + clickable links
- Index
Jexer Work Log
==============
+August 7, 2017
+
+Had trouble sleeping, what with a bunch of imaginative thoughts for
+this release. jexer.backend will be the ultimate destination for
+jexer.session and most of jexer.io. TerminalReader will be the
+interface for keyboard and mouse events. cmScreenConnected and
+cmScreenDisconnected will be new events to represent a screen
+appearing/disappearing, and MultiBackend will be a new backend
+multiplexer that goes full XRandR. Several new demos demonstrating
+multi-screen support will be coming along.
+
+August 6, 2017
+
+Time to clean up more API, particularly between Backend and Screen.
+Both of these will be interfaces soon, so that one could easily
+subclass JComponent and implement both Screen and Backend. The
+original code evolved out of Qodem, where screen.c and input.c were
+two different things leading to ECMA48Screen and ECMA48Terminal, but
+now there is really no need to keep them separate. It also
+complicates the constructors, as these are basically friend classes
+that have used package private access to get around their artificial
+separation.
+
+When I get this done it should be a lot easier to do any of:
+
+* Pass a JFrame or JComponent to SwingBackend and have it add itself,
+ like any other Swing widget.
+
+* Construct a SwingBackend and add it to any regular JComponent.
+
+* Have multiple TApplications running inside the same Swing
+ application, including having actions affect each other. (Will also
+ need to ensure that TWidgets/TWindows are not in different
+ TApplication collections.)
+
+* Build a Backend/Screen multiplexer, so that one could have a ECMA48
+ TApplication listening on a port and a local Swing monitor for it.
+
+* Build a Backend/Screen manager, so that one could have multiple
+ ECMA48 screens acting as a single large screen (e.g. XRandR).
+
+Now I need to decide which package will collect Backend, SessionInfo,
+and Screen. jexer.io has some java.io stuff, so it stays anyway.
+
July 28, 2017
Got very busy with my meatspace life, now getting a chance to come
import jexer.event.TMouseEvent;
import jexer.event.TResizeEvent;
import jexer.backend.Backend;
+import jexer.backend.Screen;
import jexer.backend.SwingBackend;
import jexer.backend.ECMA48Backend;
-import jexer.io.Screen;
import jexer.menu.TMenu;
import jexer.menu.TMenuItem;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
/**
- * TApplication sets up a full Text User Interface application.
+ * TApplication is the main driver class for a full Text User Interface
+ * application. It manages windows, provides a menu bar and status bar, and
+ * processes events received from the user.
*/
public class TApplication implements Runnable {
// Main loop --------------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Force this application to exit.
+ */
+ public void exit() {
+ quit = true;
+ }
+
/**
* Run this application until it exits.
*/
import java.util.List;
import java.util.ArrayList;
+import jexer.backend.Screen;
import jexer.bits.ColorTheme;
import jexer.event.TCommandEvent;
import jexer.event.TInputEvent;
import jexer.event.TMenuEvent;
import jexer.event.TMouseEvent;
import jexer.event.TResizeEvent;
-import jexer.io.Screen;
import jexer.menu.TMenu;
import static jexer.TKeypress.*;
import java.util.HashSet;
+import jexer.backend.Screen;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.bits.GraphicsChars;
import jexer.event.TMenuEvent;
import jexer.event.TMouseEvent;
import jexer.event.TResizeEvent;
-import jexer.io.Screen;
import jexer.menu.TMenu;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
import java.util.List;
import jexer.event.TInputEvent;
-import jexer.io.Screen;
-import jexer.session.SessionInfo;
/**
- * This abstract class provides a screen, keyboard, and mouse to
- * TApplication. It also exposes session information as gleaned from lower
- * levels of the communication stack.
+ * This interface provides a screen, keyboard, and mouse to TApplication. It
+ * also exposes session information as gleaned from lower levels of the
+ * communication stack.
*/
-public abstract class Backend {
+public interface Backend {
/**
- * The session information.
- */
- protected SessionInfo sessionInfo;
-
- /**
- * Getter for sessionInfo.
+ * Get a SessionInfo, which exposes text width/height, language,
+ * username, and other information from the communication stack.
*
* @return the SessionInfo
*/
- public final SessionInfo getSessionInfo() {
- return sessionInfo;
- }
-
- /**
- * The screen to draw on.
- */
- protected Screen screen;
+ public SessionInfo getSessionInfo();
/**
- * Getter for screen.
+ * Get a Screen, which displays the text cells to the user.
*
* @return the Screen
*/
- public final Screen getScreen() {
- return screen;
- }
+ public Screen getScreen();
/**
- * Subclasses must provide an implementation that syncs the logical
- * screen to the physical device.
+ * Classes must provide an implementation that syncs the logical screen
+ * to the physical device.
*/
- public abstract void flushScreen();
+ public void flushScreen();
/**
- * Subclasses must provide an implementation to get keyboard, mouse, and
+ * Classes must provide an implementation to get keyboard, mouse, and
* screen resize events.
*
* @param queue list to append new events to
*/
- public abstract void getEvents(List<TInputEvent> queue);
+ public void getEvents(List<TInputEvent> queue);
/**
- * Subclasses must provide an implementation that closes sockets,
- * restores console, etc.
+ * Classes must provide an implementation that closes sockets, restores
+ * console, etc.
*/
- public abstract void shutdown();
+ public void shutdown();
/**
- * Subclasses must provide an implementation that sets the window title.
+ * Classes must provide an implementation that sets the window title.
*
* @param title the new title
*/
- public abstract void setTitle(final String title);
+ public void setTitle(final String title);
}
import java.util.List;
import jexer.event.TInputEvent;
-import jexer.io.ECMA48Screen;
-import jexer.io.ECMA48Terminal;
/**
* This class uses an xterm/ANSI X3.64/ECMA-48 type terminal to provide a
* screen, keyboard, and mouse to TApplication.
*/
-public final class ECMA48Backend extends Backend {
+public final class ECMA48Backend extends GenericBackend {
/**
* Input events are processed by this Terminal.
// Keep the terminal's sessionInfo so that TApplication can see it
sessionInfo = terminal.getSessionInfo();
- // Create a screen
- screen = new ECMA48Screen(terminal);
-
- // Clear the screen
- terminal.getOutput().write(terminal.clearAll());
- terminal.flush();
+ // ECMA48Terminal is the screen too
+ screen = terminal;
}
/**
// Keep the terminal's sessionInfo so that TApplication can see it
sessionInfo = terminal.getSessionInfo();
- // Create a screen
- screen = new ECMA48Screen(terminal);
-
- // Clear the screen
- terminal.getOutput().write(terminal.clearAll());
- terminal.flush();
+ // ECMA48Terminal is the screen too
+ screen = terminal;
}
/**
*/
@Override
public void shutdown() {
- terminal.shutdown();
+ terminal.closeTerminal();
}
/**
*/
@Override
public void setTitle(final String title) {
- ((ECMA48Screen) screen).setTitle(title);
+ screen.setTitle(title);
}
}
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
-package jexer.io;
+package jexer.backend;
import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.util.List;
import java.util.LinkedList;
+import jexer.bits.Cell;
+import jexer.bits.CellAttributes;
import jexer.bits.Color;
import jexer.event.TInputEvent;
import jexer.event.TKeypressEvent;
import jexer.event.TMouseEvent;
import jexer.event.TResizeEvent;
-import jexer.session.SessionInfo;
-import jexer.session.TSessionInfo;
-import jexer.session.TTYSessionInfo;
import static jexer.TKeypress.*;
/**
* This class reads keystrokes and mouse events and emits output to ANSI
* X3.64 / ECMA-48 type terminals e.g. xterm, linux, vt100, ansi.sys, etc.
*/
-public final class ECMA48Terminal implements Runnable {
+public final class ECMA48Terminal extends LogicalScreen
+ implements TerminalReader, Runnable {
+
+ /**
+ * Emit debugging to stderr.
+ */
+ private boolean debugToStderr = false;
/**
* If true, emit T.416-style RGB colors. This is a) expensive in
public ECMA48Terminal(final Object listener, final InputStream input,
final OutputStream output) throws UnsupportedEncodingException {
- reset();
+ resetParser();
mouse1 = false;
mouse2 = false;
mouse3 = false;
eventQueue = new LinkedList<TInputEvent>();
readerThread = new Thread(this);
readerThread.start();
+
+ // Query the screen size
+ setDimensions(sessionInfo.getWindowWidth(),
+ sessionInfo.getWindowHeight());
+
+ // Clear the screen
+ this.output.write(clearAll());
+ this.output.flush();
}
/**
if (writer == null) {
throw new IllegalArgumentException("Writer must be specified");
}
- reset();
+ resetParser();
mouse1 = false;
mouse2 = false;
mouse3 = false;
eventQueue = new LinkedList<TInputEvent>();
readerThread = new Thread(this);
readerThread.start();
+
+ // Query the screen size
+ setDimensions(sessionInfo.getWindowWidth(),
+ sessionInfo.getWindowHeight());
+
+ // Clear the screen
+ this.output.write(clearAll());
+ this.output.flush();
}
/**
/**
* Restore terminal to normal state.
*/
- public void shutdown() {
+ public void closeTerminal() {
// System.err.println("=== shutdown() ==="); System.err.flush();
output.flush();
}
+ /**
+ * Perform a somewhat-optimal rendering of a line.
+ *
+ * @param y row coordinate. 0 is the top-most row.
+ * @param sb StringBuilder to write escape sequences to
+ * @param lastAttr cell attributes from the last call to flushLine
+ */
+ private void flushLine(final int y, final StringBuilder sb,
+ CellAttributes lastAttr) {
+
+ int lastX = -1;
+ int textEnd = 0;
+ for (int x = 0; x < width; x++) {
+ Cell lCell = logical[x][y];
+ if (!lCell.isBlank()) {
+ textEnd = x;
+ }
+ }
+ // Push textEnd to first column beyond the text area
+ textEnd++;
+
+ // DEBUG
+ // reallyCleared = true;
+
+ for (int x = 0; x < width; x++) {
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
+
+ if (!lCell.equals(pCell) || reallyCleared) {
+
+ if (debugToStderr) {
+ System.err.printf("\n--\n");
+ System.err.printf(" Y: %d X: %d\n", y, x);
+ System.err.printf(" lCell: %s\n", lCell);
+ System.err.printf(" pCell: %s\n", pCell);
+ System.err.printf(" ==== \n");
+ }
+
+ if (lastAttr == null) {
+ lastAttr = new CellAttributes();
+ sb.append(normal());
+ }
+
+ // Place the cell
+ if ((lastX != (x - 1)) || (lastX == -1)) {
+ // Advancing at least one cell, or the first gotoXY
+ sb.append(gotoXY(x, y));
+ }
+
+ assert (lastAttr != null);
+
+ if ((x == textEnd) && (textEnd < width - 1)) {
+ assert (lCell.isBlank());
+
+ for (int i = x; i < width; i++) {
+ assert (logical[i][y].isBlank());
+ // Physical is always updated
+ physical[i][y].reset();
+ }
+
+ // Clear remaining line
+ sb.append(clearRemainingLine());
+ lastAttr.reset();
+ return;
+ }
+
+ // Now emit only the modified attributes
+ if ((lCell.getForeColor() != lastAttr.getForeColor())
+ && (lCell.getBackColor() != lastAttr.getBackColor())
+ && (lCell.isBold() == lastAttr.isBold())
+ && (lCell.isReverse() == lastAttr.isReverse())
+ && (lCell.isUnderline() == lastAttr.isUnderline())
+ && (lCell.isBlink() == lastAttr.isBlink())
+ ) {
+ // Both colors changed, attributes the same
+ sb.append(color(lCell.isBold(),
+ lCell.getForeColor(), lCell.getBackColor()));
+
+ if (debugToStderr) {
+ System.err.printf("1 Change only fore/back colors\n");
+ }
+ } else if ((lCell.getForeColor() != lastAttr.getForeColor())
+ && (lCell.getBackColor() != lastAttr.getBackColor())
+ && (lCell.isBold() != lastAttr.isBold())
+ && (lCell.isReverse() != lastAttr.isReverse())
+ && (lCell.isUnderline() != lastAttr.isUnderline())
+ && (lCell.isBlink() != lastAttr.isBlink())
+ ) {
+ // Everything is different
+ sb.append(color(lCell.getForeColor(),
+ lCell.getBackColor(),
+ lCell.isBold(), lCell.isReverse(),
+ lCell.isBlink(),
+ lCell.isUnderline()));
+
+ if (debugToStderr) {
+ System.err.printf("2 Set all attributes\n");
+ }
+ } else if ((lCell.getForeColor() != lastAttr.getForeColor())
+ && (lCell.getBackColor() == lastAttr.getBackColor())
+ && (lCell.isBold() == lastAttr.isBold())
+ && (lCell.isReverse() == lastAttr.isReverse())
+ && (lCell.isUnderline() == lastAttr.isUnderline())
+ && (lCell.isBlink() == lastAttr.isBlink())
+ ) {
+
+ // Attributes same, foreColor different
+ sb.append(color(lCell.isBold(),
+ lCell.getForeColor(), true));
+
+ if (debugToStderr) {
+ System.err.printf("3 Change foreColor\n");
+ }
+ } else if ((lCell.getForeColor() == lastAttr.getForeColor())
+ && (lCell.getBackColor() != lastAttr.getBackColor())
+ && (lCell.isBold() == lastAttr.isBold())
+ && (lCell.isReverse() == lastAttr.isReverse())
+ && (lCell.isUnderline() == lastAttr.isUnderline())
+ && (lCell.isBlink() == lastAttr.isBlink())
+ ) {
+ // Attributes same, backColor different
+ sb.append(color(lCell.isBold(),
+ lCell.getBackColor(), false));
+
+ if (debugToStderr) {
+ System.err.printf("4 Change backColor\n");
+ }
+ } else if ((lCell.getForeColor() == lastAttr.getForeColor())
+ && (lCell.getBackColor() == lastAttr.getBackColor())
+ && (lCell.isBold() == lastAttr.isBold())
+ && (lCell.isReverse() == lastAttr.isReverse())
+ && (lCell.isUnderline() == lastAttr.isUnderline())
+ && (lCell.isBlink() == lastAttr.isBlink())
+ ) {
+
+ // All attributes the same, just print the char
+ // NOP
+
+ if (debugToStderr) {
+ System.err.printf("5 Only emit character\n");
+ }
+ } else {
+ // Just reset everything again
+ sb.append(color(lCell.getForeColor(),
+ lCell.getBackColor(),
+ lCell.isBold(),
+ lCell.isReverse(),
+ lCell.isBlink(),
+ lCell.isUnderline()));
+
+ if (debugToStderr) {
+ System.err.printf("6 Change all attributes\n");
+ }
+ }
+ // Emit the character
+ sb.append(lCell.getChar());
+
+ // Save the last rendered cell
+ lastX = x;
+ lastAttr.setTo(lCell);
+
+ // Physical is always updated
+ physical[x][y].setTo(lCell);
+
+ } // if (!lCell.equals(pCell) || (reallyCleared == true))
+
+ } // for (int x = 0; x < width; x++)
+ }
+
+ /**
+ * Render the screen to a string that can be emitted to something that
+ * knows how to process ECMA-48/ANSI X3.64 escape sequences.
+ *
+ * @return escape sequences string that provides the updates to the
+ * physical screen
+ */
+ private String flushString() {
+ if (!dirty) {
+ assert (!reallyCleared);
+ return "";
+ }
+
+ CellAttributes attr = null;
+
+ StringBuilder sb = new StringBuilder();
+ if (reallyCleared) {
+ attr = new CellAttributes();
+ sb.append(clearAll());
+ }
+
+ for (int y = 0; y < height; y++) {
+ flushLine(y, sb, attr);
+ }
+
+ dirty = false;
+ reallyCleared = false;
+
+ String result = sb.toString();
+ if (debugToStderr) {
+ System.err.printf("flushString(): %s\n", result);
+ }
+ return result;
+ }
+
+ /**
+ * Push the logical screen to the physical device.
+ */
+ @Override
+ public void flushPhysical() {
+ String result = flushString();
+ if ((cursorVisible)
+ && (cursorY <= height - 1)
+ && (cursorX <= width - 1)
+ ) {
+ result += cursor(true);
+ result += gotoXY(cursorX, cursorY);
+ } else {
+ result += cursor(false);
+ }
+ output.write(result);
+ flush();
+ }
+
+ /**
+ * Set the window title.
+ *
+ * @param title the new title
+ */
+ public void setTitle(final String title) {
+ output.write(getSetTitleString(title));
+ flush();
+ }
+
/**
* Reset keyboard/mouse input parser.
*/
- private void reset() {
+ private void resetParser() {
state = ParseState.GROUND;
params = new ArrayList<String>();
params.clear();
if (escDelay > 100) {
// After 0.1 seconds, assume a true escape character
queue.add(controlChar((char)0x1B, false));
- reset();
+ resetParser();
}
}
}
if (escDelay > 250) {
// After 0.25 seconds, assume a true escape character
events.add(controlChar((char)0x1B, false));
- reset();
+ resetParser();
}
}
if (ch <= 0x1F) {
// Control character
events.add(controlChar(ch, false));
- reset();
+ resetParser();
return;
}
// Normal character
events.add(new TKeypressEvent(false, 0, ch,
false, false, false));
- reset();
+ resetParser();
return;
}
if (ch <= 0x1F) {
// ALT-Control character
events.add(controlChar(ch, true));
- reset();
+ resetParser();
return;
}
}
alt = true;
events.add(new TKeypressEvent(false, 0, ch, alt, ctrl, shift));
- reset();
+ resetParser();
return;
case ESCAPE_INTERMEDIATE:
default:
break;
}
- reset();
+ resetParser();
return;
}
// Unknown keystroke, ignore
- reset();
+ resetParser();
return;
case CSI_ENTRY:
case 'A':
// Up
events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
- reset();
+ resetParser();
return;
case 'B':
// Down
events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
- reset();
+ resetParser();
return;
case 'C':
// Right
events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
- reset();
+ resetParser();
return;
case 'D':
// Left
events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
- reset();
+ resetParser();
return;
case 'H':
// Home
events.add(new TKeypressEvent(kbHome));
- reset();
+ resetParser();
return;
case 'F':
// End
events.add(new TKeypressEvent(kbEnd));
- reset();
+ resetParser();
return;
case 'Z':
// CBT - Cursor backward X tab stops (default 1)
events.add(new TKeypressEvent(kbBackTab));
- reset();
+ resetParser();
return;
case 'M':
// Mouse position
}
// Unknown keystroke, ignore
- reset();
+ resetParser();
return;
case MOUSE_SGR:
if (event != null) {
events.add(event);
}
- reset();
+ resetParser();
return;
case 'm':
// Generate a mouse release event
if (event != null) {
events.add(event);
}
- reset();
+ resetParser();
return;
default:
break;
}
// Unknown keystroke, ignore
- reset();
+ resetParser();
return;
case CSI_PARAM:
if (ch == '~') {
events.add(csiFnKey());
- reset();
+ resetParser();
return;
}
ctrl = csiIsCtrl(params.get(1));
}
events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
- reset();
+ resetParser();
return;
case 'B':
// Down
ctrl = csiIsCtrl(params.get(1));
}
events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
- reset();
+ resetParser();
return;
case 'C':
// Right
ctrl = csiIsCtrl(params.get(1));
}
events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
- reset();
+ resetParser();
return;
case 'D':
// Left
ctrl = csiIsCtrl(params.get(1));
}
events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
- reset();
+ resetParser();
return;
case 'H':
// Home
ctrl = csiIsCtrl(params.get(1));
}
events.add(new TKeypressEvent(kbHome, alt, ctrl, shift));
- reset();
+ resetParser();
return;
case 'F':
// End
ctrl = csiIsCtrl(params.get(1));
}
events.add(new TKeypressEvent(kbEnd, alt, ctrl, shift));
- reset();
+ resetParser();
return;
default:
break;
}
// Unknown keystroke, ignore
- reset();
+ resetParser();
return;
case MOUSE:
if (params.get(0).length() == 3) {
// We have enough to generate a mouse event
events.add(parseMouse());
- reset();
+ resetParser();
}
return;
}
/**
- * Create an xterm OSC sequence to change the window title. Note package
- * private access.
+ * Create an xterm OSC sequence to change the window title.
*
* @param title the new title
* @return the string to emit to xterm
*/
- String setTitle(final String title) {
+ private String getSetTitleString(final String title) {
return "\033]2;" + title + "\007";
}
/**
- * Create a SGR parameter sequence for a single color change. Note
- * package private access.
+ * Create a SGR parameter sequence for a single color change.
*
* @param bold if true, set bold
* @param color one of the Color.WHITE, Color.BLUE, etc. constants
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[42m"
*/
- String color(final boolean bold, final Color color,
+ private String color(final boolean bold, final Color color,
final boolean foreground) {
return color(color, foreground, true) +
rgbColor(bold, color, foreground);
/**
* Create a SGR parameter sequence for both foreground and background
- * color change. Note package private access.
+ * color change.
*
* @param bold if true, set bold
* @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[31;42m"
*/
- String color(final boolean bold, final Color foreColor,
+ private String color(final boolean bold, final Color foreColor,
final Color backColor) {
return color(foreColor, backColor, true) +
rgbColor(bold, foreColor, backColor);
/**
* Create a SGR parameter sequence for foreground, background, and
* several attributes. This sequence first resets all attributes to
- * default, then sets attributes as per the parameters. Note package
- * private access.
+ * default, then sets attributes as per the parameters.
*
* @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
* @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[0;1;31;42m"
*/
- String color(final Color foreColor, final Color backColor,
+ private String color(final Color foreColor, final Color backColor,
final boolean bold, final boolean reverse, final boolean blink,
final boolean underline) {
}
/**
- * Create a SGR parameter sequence to reset to defaults. Note package
- * private access.
+ * Create a SGR parameter sequence to reset to defaults.
*
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[0m"
*/
- String normal() {
+ private String normal() {
return normal(true) + rgbColor(false, Color.WHITE, Color.BLACK);
}
}
/**
- * Create a SGR parameter sequence for enabling the visible cursor. Note
- * package private access.
+ * Create a SGR parameter sequence for enabling the visible cursor.
*
* @param on if true, turn on cursor
* @return the string to emit to an ANSI / ECMA-style terminal
*/
- String cursor(final boolean on) {
+ private String cursor(final boolean on) {
if (on && !cursorOn) {
cursorOn = true;
return "\033[?25h";
*
* @return the string to emit to an ANSI / ECMA-style terminal
*/
- public String clearAll() {
+ private String clearAll() {
return "\033[0;37;40m\033[2J";
}
/**
* Clear the line from the cursor (inclusive) to the end of the screen.
* Because some terminals use back-color-erase, set the color to
- * white-on-black beforehand. Note package private access.
+ * white-on-black beforehand.
*
* @return the string to emit to an ANSI / ECMA-style terminal
*/
- String clearRemainingLine() {
+ private String clearRemainingLine() {
return "\033[0;37;40m\033[K";
}
/**
- * Move the cursor to (x, y). Note package private access.
+ * Move the cursor to (x, y).
*
* @param x column coordinate. 0 is the left-most column.
* @param y row coordinate. 0 is the top-most row.
* @return the string to emit to an ANSI / ECMA-style terminal
*/
- String gotoXY(final int x, final int y) {
+ private String gotoXY(final int x, final int y) {
return String.format("\033[%d;%dH", y + 1, x + 1);
}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin 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 Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer.backend;
+
+import java.util.List;
+
+import jexer.event.TInputEvent;
+
+/**
+ * This abstract class provides a screen, keyboard, and mouse to
+ * TApplication. It also exposes session information as gleaned from lower
+ * levels of the communication stack.
+ */
+public abstract class GenericBackend implements Backend {
+
+ /**
+ * The session information.
+ */
+ protected SessionInfo sessionInfo;
+
+ /**
+ * Getter for sessionInfo.
+ *
+ * @return the SessionInfo
+ */
+ public final SessionInfo getSessionInfo() {
+ return sessionInfo;
+ }
+
+ /**
+ * The screen to draw on.
+ */
+ protected Screen screen;
+
+ /**
+ * Getter for screen.
+ *
+ * @return the Screen
+ */
+ public final Screen getScreen() {
+ return screen;
+ }
+
+ /**
+ * Subclasses must provide an implementation that syncs the logical
+ * screen to the physical device.
+ */
+ public abstract void flushScreen();
+
+ /**
+ * Subclasses must provide an implementation to get keyboard, mouse, and
+ * screen resize events.
+ *
+ * @param queue list to append new events to
+ */
+ public abstract void getEvents(List<TInputEvent> queue);
+
+ /**
+ * Subclasses must provide an implementation that closes sockets,
+ * restores console, etc.
+ */
+ public abstract void shutdown();
+
+ /**
+ * Subclasses must provide an implementation that sets the window title.
+ *
+ * @param title the new title
+ */
+ public abstract void setTitle(final String title);
+
+}
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
-package jexer.io;
+package jexer.backend;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.bits.GraphicsChars;
/**
- * This class represents a text-based screen. Drawing operations write to a
- * logical screen.
+ * A logical screen composed of a 2D array of Cells.
*/
-public abstract class Screen {
+public class LogicalScreen implements Screen {
/**
* Width of the visible window.
/**
* Public constructor. Sets everything to not-bold, white-on-black.
*/
- protected Screen() {
+ protected LogicalScreen() {
offsetX = 0;
offsetY = 0;
width = 80;
}
/**
- * Subclasses must provide an implementation to push the logical screen
- * to the physical device.
+ * Default implementation does nothing.
*/
- public abstract void flushPhysical();
+ public void flushPhysical() {}
/**
* Put the cursor at (x,y).
public final void hideCursor() {
cursorVisible = false;
}
+
+ /**
+ * Set the window title. Default implementation does nothing.
+ *
+ * @param title the new title
+ */
+ public void setTitle(final String title) {}
+
}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin 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 Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer.backend;
+
+import jexer.bits.Cell;
+import jexer.bits.CellAttributes;
+
+/**
+ * Drawing operations API.
+ */
+public interface Screen {
+
+ /**
+ * Set drawing offset for x.
+ *
+ * @param offsetX new drawing offset
+ */
+ public void setOffsetX(final int offsetX);
+
+ /**
+ * Set drawing offset for y.
+ *
+ * @param offsetY new drawing offset
+ */
+ public void setOffsetY(final int offsetY);
+
+ /**
+ * Get right drawing clipping boundary.
+ *
+ * @return drawing boundary
+ */
+ public int getClipRight();
+
+ /**
+ * Set right drawing clipping boundary.
+ *
+ * @param clipRight new boundary
+ */
+ public void setClipRight(final int clipRight);
+
+ /**
+ * Get bottom drawing clipping boundary.
+ *
+ * @return drawing boundary
+ */
+ public int getClipBottom();
+
+ /**
+ * Set bottom drawing clipping boundary.
+ *
+ * @param clipBottom new boundary
+ */
+ public void setClipBottom(final int clipBottom);
+
+ /**
+ * Get left drawing clipping boundary.
+ *
+ * @return drawing boundary
+ */
+ public int getClipLeft();
+
+ /**
+ * Set left drawing clipping boundary.
+ *
+ * @param clipLeft new boundary
+ */
+ public void setClipLeft(final int clipLeft);
+
+ /**
+ * Get top drawing clipping boundary.
+ *
+ * @return drawing boundary
+ */
+ public int getClipTop();
+
+ /**
+ * Set top drawing clipping boundary.
+ *
+ * @param clipTop new boundary
+ */
+ public void setClipTop(final int clipTop);
+
+ /**
+ * Get dirty flag.
+ *
+ * @return if true, the logical screen is not in sync with the physical
+ * screen
+ */
+ public boolean isDirty();
+
+ /**
+ * Get the attributes at one location.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @return attributes at (x, y)
+ */
+ public CellAttributes getAttrXY(final int x, final int y);
+
+ /**
+ * Set the attributes at one location.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ public void putAttrXY(final int x, final int y,
+ final CellAttributes attr);
+
+ /**
+ * Set the attributes at one location.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param attr attributes to use (bold, foreColor, backColor)
+ * @param clip if true, honor clipping/offset
+ */
+ public void putAttrXY(final int x, final int y,
+ final CellAttributes attr, final boolean clip);
+
+ /**
+ * Fill the entire screen with one character with attributes.
+ *
+ * @param ch character to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ public void putAll(final char ch, final CellAttributes attr);
+
+ /**
+ * Render one character with attributes.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param ch character + attributes to draw
+ */
+ public void putCharXY(final int x, final int y, final Cell ch);
+
+ /**
+ * Render one character with attributes.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param ch character to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ public void putCharXY(final int x, final int y, final char ch,
+ final CellAttributes attr);
+
+ /**
+ * Render one character without changing the underlying attributes.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param ch character to draw
+ */
+ public void putCharXY(final int x, final int y, final char ch);
+
+ /**
+ * Render a string. Does not wrap if the string exceeds the line.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param str string to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ public void putStringXY(final int x, final int y, final String str,
+ final CellAttributes attr);
+
+ /**
+ * Render a string without changing the underlying attribute. Does not
+ * wrap if the string exceeds the line.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param str string to draw
+ */
+ public void putStringXY(final int x, final int y, final String str);
+
+ /**
+ * Draw a vertical line from (x, y) to (x, y + n).
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param n number of characters to draw
+ * @param ch character to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ public void vLineXY(final int x, final int y, final int n,
+ final char ch, final CellAttributes attr);
+
+ /**
+ * Draw a horizontal line from (x, y) to (x + n, y).
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param n number of characters to draw
+ * @param ch character to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ public void hLineXY(final int x, final int y, final int n,
+ final char ch, final CellAttributes attr);
+
+ /**
+ * Change the width. Everything on-screen will be destroyed and must be
+ * redrawn.
+ *
+ * @param width new screen width
+ */
+ public void setWidth(final int width);
+
+ /**
+ * Change the height. Everything on-screen will be destroyed and must be
+ * redrawn.
+ *
+ * @param height new screen height
+ */
+ public void setHeight(final int height);
+
+ /**
+ * Change the width and height. Everything on-screen will be destroyed
+ * and must be redrawn.
+ *
+ * @param width new screen width
+ * @param height new screen height
+ */
+ public void setDimensions(final int width, final int height);
+
+ /**
+ * Get the height.
+ *
+ * @return current screen height
+ */
+ public int getHeight();
+
+ /**
+ * Get the width.
+ *
+ * @return current screen width
+ */
+ public int getWidth();
+
+ /**
+ * Reset screen to not-bold, white-on-black. Also flushes the offset and
+ * clip variables.
+ */
+ public void reset();
+
+ /**
+ * Flush the offset and clip variables.
+ */
+ public void resetClipping();
+
+ /**
+ * Clear the logical screen.
+ */
+ public void clear();
+
+ /**
+ * Draw a box with a border and empty background.
+ *
+ * @param left left column of box. 0 is the left-most row.
+ * @param top top row of the box. 0 is the top-most row.
+ * @param right right column of box
+ * @param bottom bottom row of the box
+ * @param border attributes to use for the border
+ * @param background attributes to use for the background
+ */
+ public void drawBox(final int left, final int top,
+ final int right, final int bottom,
+ final CellAttributes border, final CellAttributes background);
+
+ /**
+ * Draw a box with a border and empty background.
+ *
+ * @param left left column of box. 0 is the left-most row.
+ * @param top top row of the box. 0 is the top-most row.
+ * @param right right column of box
+ * @param bottom bottom row of the box
+ * @param border attributes to use for the border
+ * @param background attributes to use for the background
+ * @param borderType if 1, draw a single-line border; if 2, draw a
+ * double-line border; if 3, draw double-line top/bottom edges and
+ * single-line left/right edges (like Qmodem)
+ * @param shadow if true, draw a "shadow" on the box
+ */
+ public void drawBox(final int left, final int top,
+ final int right, final int bottom,
+ final CellAttributes border, final CellAttributes background,
+ final int borderType, final boolean shadow);
+
+ /**
+ * Draw a box shadow.
+ *
+ * @param left left column of box. 0 is the left-most row.
+ * @param top top row of the box. 0 is the top-most row.
+ * @param right right column of box
+ * @param bottom bottom row of the box
+ */
+ public void drawBoxShadow(final int left, final int top,
+ final int right, final int bottom);
+
+ /**
+ * Classes must provide an implementation to push the logical screen to
+ * the physical device.
+ */
+ public void flushPhysical();
+
+ /**
+ * Put the cursor at (x,y).
+ *
+ * @param visible if true, the cursor should be visible
+ * @param x column coordinate to put the cursor on
+ * @param y row coordinate to put the cursor on
+ */
+ public void putCursor(final boolean visible, final int x, final int y);
+
+ /**
+ * Hide the cursor.
+ */
+ public void hideCursor();
+
+ /**
+ * Set the window title.
+ *
+ * @param title the new title
+ */
+ public void setTitle(final String title);
+
+}
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
-package jexer.session;
+package jexer.backend;
/**
* SessionInfo is used to store per-session properties that are determined at
package jexer.backend;
import java.util.List;
+import javax.swing.JComponent;
import jexer.event.TInputEvent;
-import jexer.io.SwingScreen;
-import jexer.io.SwingTerminal;
/**
* This class uses standard Swing calls to handle screen, keyboard, and mouse
* I/O.
*/
-public final class SwingBackend extends Backend {
+public final class SwingBackend extends GenericBackend {
/**
* Input events are processed by this Terminal.
}
/**
- * Public constructor.
+ * Public constructor will spawn a new JFrame.
*
* @param listener the object this backend needs to wake up when new
* input comes in
public SwingBackend(final Object listener, final int windowWidth,
final int windowHeight, final int fontSize) {
- // Create a screen
- SwingScreen screen = new SwingScreen(windowWidth, windowHeight,
- fontSize);
- this.screen = screen;
+ // Create a Swing backend using a JFrame
+ terminal = new SwingTerminal(windowWidth, windowHeight, fontSize,
+ listener);
- // Create the Swing event listeners
- terminal = new SwingTerminal(listener, screen);
+ // Hang onto the session info
+ this.sessionInfo = terminal.getSessionInfo();
+
+ // SwingTerminal is the screen too
+ screen = terminal;
+ }
+
+ /**
+ * Public constructor will render onto a JComponent.
+ *
+ * @param component the Swing component to render to
+ * @param listener the object this backend needs to wake up when new
+ * input comes in
+ * @param windowWidth the number of text columns to start with
+ * @param windowHeight the number of text rows to start with
+ * @param fontSize the size in points. Good values to pick are: 16, 20,
+ * 22, and 24.
+ */
+ public SwingBackend(final JComponent component, final Object listener,
+ final int windowWidth, final int windowHeight, final int fontSize) {
+
+ // Create a Swing backend using a JComponent
+ terminal = new SwingTerminal(component, windowWidth, windowHeight,
+ fontSize, listener);
// Hang onto the session info
this.sessionInfo = terminal.getSessionInfo();
+
+ // SwingTerminal is the screen too
+ screen = terminal;
}
/**
*/
@Override
public void shutdown() {
- ((SwingScreen) screen).shutdown();
+ terminal.closeTerminal();
}
/**
*/
@Override
public void setTitle(final String title) {
- ((SwingScreen) screen).setTitle(title);
+ screen.setTitle(title);
+ }
+
+ /**
+ * Set listener to a different Object.
+ *
+ * @param listener the new listening object that run() wakes up on new
+ * input
+ */
+ public void setListener(final Object listener) {
+ terminal.setListener(listener);
}
}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin 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 Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer.backend;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ComponentListener;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelListener;
+import java.awt.event.WindowListener;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferStrategy;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+
+/**
+ * Wrapper for integrating with Swing, because JFrame and JComponent have
+ * separate hierarchies.
+ */
+class SwingComponent {
+
+ /**
+ * If true, use triple buffering when drawing to a JFrame.
+ */
+ public static boolean tripleBuffer = false;
+
+ /**
+ * Get the BufferStrategy object needed for triple-buffering.
+ *
+ * @return the BufferStrategy
+ * @throws IllegalArgumentException if this function is called when
+ * not rendering to a JFrame
+ */
+ public BufferStrategy getBufferStrategy() {
+ if (frame != null) {
+ return frame.getBufferStrategy();
+ } else {
+ throw new IllegalArgumentException("BufferStrategy not used " +
+ "for JComponent access");
+ }
+ }
+
+ /**
+ * The frame reference, if we are drawing to a JFrame.
+ */
+ private JFrame frame;
+
+ /**
+ * The component reference, if we are drawing to a JComponent.
+ */
+ private JComponent component;
+
+ /**
+ * Get the JFrame reference.
+ *
+ * @return the frame, or null if this is drawing to a JComponent
+ */
+ public JFrame getFrame() {
+ return frame;
+ }
+
+ /**
+ * Get the JComponent reference.
+ *
+ * @return the component, or null if this is drawing to a JFrame
+ */
+ public JComponent getComponent() {
+ return component;
+ }
+
+ /**
+ * Construct using a JFrame.
+ *
+ * @param frame the JFrame to draw to
+ */
+ public SwingComponent(final JFrame frame) {
+ this.frame = frame;
+ setupFrame();
+ }
+
+ /**
+ * Construct using a JComponent.
+ *
+ * @param component the JComponent to draw to
+ */
+ public SwingComponent(final JComponent component) {
+ this.component = component;
+ setupComponent();
+ }
+
+ /**
+ * Setup to render to an existing JComponent.
+ */
+ public void setupComponent() {
+ component.setBackground(Color.black);
+
+ // Kill the X11 cursor
+ // Transparent 16 x 16 pixel cursor image.
+ BufferedImage cursorImg = new BufferedImage(16, 16,
+ BufferedImage.TYPE_INT_ARGB);
+ // Create a new blank cursor.
+ Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
+ cursorImg, new Point(0, 0), "blank cursor");
+ component.setCursor(blankCursor);
+
+ // Be capable of seeing Tab / Shift-Tab
+ component.setFocusTraversalKeysEnabled(false);
+ }
+
+ /**
+ * Setup to render to an existing JFrame.
+ */
+ public void setupFrame() {
+ frame.setTitle("Jexer Application");
+ frame.setBackground(Color.black);
+ frame.pack();
+
+ // Kill the X11 cursor
+ // Transparent 16 x 16 pixel cursor image.
+ BufferedImage cursorImg = new BufferedImage(16, 16,
+ BufferedImage.TYPE_INT_ARGB);
+ // Create a new blank cursor.
+ Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
+ cursorImg, new Point(0, 0), "blank cursor");
+ frame.setCursor(blankCursor);
+
+ // Be capable of seeing Tab / Shift-Tab
+ frame.setFocusTraversalKeysEnabled(false);
+
+ // Setup triple-buffering
+ if (tripleBuffer) {
+ frame.setIgnoreRepaint(true);
+ frame.createBufferStrategy(3);
+ }
+ }
+
+ /**
+ * Set the window title.
+ *
+ * @param title the new title
+ */
+ public void setTitle(final String title) {
+ if (frame != null) {
+ frame.setTitle(title);
+ }
+ }
+
+ /**
+ * Paints this component.
+ *
+ * @param g the graphics context to use for painting
+ */
+ public void paint(Graphics g) {
+ if (frame != null) {
+ frame.paint(g);
+ } else {
+ component.paint(g);
+ }
+ }
+
+ /**
+ * Repaints this component.
+ */
+ public void repaint() {
+ if (frame != null) {
+ frame.repaint();
+ } else {
+ component.repaint();
+ }
+ }
+
+ /**
+ * Repaints the specified rectangle of this component.
+ *
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param width the width
+ * @param height the height
+ */
+ public void repaint(int x, int y, int width, int height) {
+ if (frame != null) {
+ frame.repaint(x, y, width, height);
+ } else {
+ component.repaint(x, y, width, height);
+ }
+ }
+
+ /**
+ * If a border has been set on this component, returns the border's
+ * insets; otherwise calls super.getInsets.
+ *
+ * @return the value of the insets property
+ */
+ public Insets getInsets() {
+ if (frame != null) {
+ return frame.getInsets();
+ } else {
+ return component.getInsets();
+ }
+ }
+
+ /**
+ * Returns the current width of this component.
+ *
+ * @return the current width of this component
+ */
+ public int getWidth() {
+ if (frame != null) {
+ return frame.getWidth();
+ } else {
+ return component.getWidth();
+ }
+ }
+
+ /**
+ * Returns the current height of this component.
+ *
+ * @return the current height of this component
+ */
+ public int getHeight() {
+ if (frame != null) {
+ return frame.getHeight();
+ } else {
+ return component.getHeight();
+ }
+ }
+
+ /**
+ * Gets the font of this component.
+ *
+ * @return this component's font; if a font has not been set for this
+ * component, the font of its parent is returned
+ */
+ public Font getFont() {
+ if (frame != null) {
+ return frame.getFont();
+ } else {
+ return component.getFont();
+ }
+ }
+
+ /**
+ * Sets the font of this component.
+ *
+ * @param f the font to become this component's font; if this parameter
+ * is null then this component will inherit the font of its parent
+ */
+ public void setFont(final Font f) {
+ if (frame != null) {
+ frame.setFont(f);
+ } else {
+ component.setFont(f);
+ }
+ }
+
+ /**
+ * Shows or hides this Window depending on the value of parameter b.
+ *
+ * @param b if true, make visible, else make invisible
+ */
+ public void setVisible(final boolean b) {
+ if (frame != null) {
+ frame.setVisible(b);
+ } else {
+ component.setVisible(b);
+ }
+ }
+
+ /**
+ * Creates a graphics context for this component. This method will return
+ * null if this component is currently not displayable.
+ *
+ * @return a graphics context for this component, or null if it has none
+ */
+ public Graphics getGraphics() {
+ if (frame != null) {
+ return frame.getGraphics();
+ } else {
+ return component.getGraphics();
+ }
+ }
+
+ /**
+ * Releases all of the native screen resources used by this Window, its
+ * subcomponents, and all of its owned children. That is, the resources
+ * for these Components will be destroyed, any memory they consume will
+ * be returned to the OS, and they will be marked as undisplayable.
+ */
+ public void dispose() {
+ if (frame != null) {
+ frame.dispose();
+ } else {
+ component.getParent().remove(component);
+ }
+ }
+
+ /**
+ * Resize the component to match the font dimensions.
+ *
+ * @param width the new width in pixels
+ * @param height the new height in pixels
+ */
+ public void setDimensions(final int width, final int height) {
+ // Figure out the thickness of borders and use that to set the final
+ // size.
+ Insets insets = getInsets();
+
+ if (frame != null) {
+ frame.setSize(width + insets.left + insets.right,
+ height + insets.top + insets.bottom);
+ } else {
+ component.setSize(width + insets.left + insets.right,
+ height + insets.top + insets.bottom);
+ }
+ }
+
+ /**
+ * Adds the specified component listener to receive component events from
+ * this component. If listener l is null, no exception is thrown and no
+ * action is performed.
+ *
+ * @param l the component listener
+ */
+ public void addComponentListener(ComponentListener l) {
+ if (frame != null) {
+ frame.addComponentListener(l);
+ } else {
+ component.addComponentListener(l);
+ }
+ }
+
+ /**
+ * Adds the specified key listener to receive key events from this
+ * component. If l is null, no exception is thrown and no action is
+ * performed.
+ *
+ * @param l the key listener.
+ */
+ public void addKeyListener(KeyListener l) {
+ if (frame != null) {
+ frame.addKeyListener(l);
+ } else {
+ component.addKeyListener(l);
+ }
+ }
+
+ /**
+ * Adds the specified mouse listener to receive mouse events from this
+ * component. If listener l is null, no exception is thrown and no action
+ * is performed.
+ *
+ * @param l the mouse listener
+ */
+ public void addMouseListener(MouseListener l) {
+ if (frame != null) {
+ frame.addMouseListener(l);
+ } else {
+ component.addMouseListener(l);
+ }
+ }
+
+ /**
+ * Adds the specified mouse motion listener to receive mouse motion
+ * events from this component. If listener l is null, no exception is
+ * thrown and no action is performed.
+ *
+ * @param l the mouse motion listener
+ */
+ public void addMouseMotionListener(MouseMotionListener l) {
+ if (frame != null) {
+ frame.addMouseMotionListener(l);
+ } else {
+ component.addMouseMotionListener(l);
+ }
+ }
+
+ /**
+ * Adds the specified mouse wheel listener to receive mouse wheel events
+ * from this component. Containers also receive mouse wheel events from
+ * sub-components.
+ *
+ * @param l the mouse wheel listener
+ */
+ public void addMouseWheelListener(MouseWheelListener l) {
+ if (frame != null) {
+ frame.addMouseWheelListener(l);
+ } else {
+ component.addMouseWheelListener(l);
+ }
+ }
+
+ /**
+ * Adds the specified window listener to receive window events from this
+ * window. If l is null, no exception is thrown and no action is
+ * performed.
+ *
+ * @param l the window listener
+ */
+ public void addWindowListener(WindowListener l) {
+ if (frame != null) {
+ frame.addWindowListener(l);
+ }
+ }
+
+}
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
-package jexer.session;
+package jexer.backend;
-import java.awt.Frame;
import java.awt.Insets;
/**
- * SwingSessionInfo provides a session implementation with a callback into an
- * Swing Frame to support queryWindowSize(). The username is blank, language
- * is "en_US", with a 132x40 text window.
+ * SwingSessionInfo provides a session implementation with a callback into
+ * Swing to support queryWindowSize(). The username is blank, language is
+ * "en_US", with a 80x25 text window.
*/
public final class SwingSessionInfo implements SessionInfo {
/**
- * The Swing Frame.
+ * The Swing JFrame or JComponent.
*/
- private Frame frame;
+ private SwingComponent swing;
/**
* The width of a text cell in pixels.
*/
- private int textWidth;
+ private int textWidth = 10;
/**
* The height of a text cell in pixels.
*/
- private int textHeight;
+ private int textHeight = 10;
/**
* User name.
return windowHeight;
}
+ /**
+ * Set the dimensions of a single text cell.
+ *
+ * @param textWidth the width of a cell in pixels
+ * @param textHeight the height of a cell in pixels
+ */
+ public void setTextCellDimensions(final int textWidth,
+ final int textHeight) {
+
+ this.textWidth = textWidth;
+ this.textHeight = textHeight;
+ }
+
/**
* Public constructor.
*
- * @param frame the Swing Frame
+ * @param swing the Swing JFrame or JComponent
* @param textWidth the width of a cell in pixels
* @param textHeight the height of a cell in pixels
- * @param windowWidth the number of text columns to start with
- * @param windowHeight the number of text rows to start with
*/
- public SwingSessionInfo(final Frame frame, final int textWidth,
- final int textHeight, final int windowWidth, final int windowHeight) {
+ public SwingSessionInfo(final SwingComponent swing, final int textWidth,
+ final int textHeight) {
- this.frame = frame;
- this.textWidth = textWidth;
- this.textHeight = textHeight;
- this.windowWidth = windowWidth;
- this.windowHeight = windowHeight;
+ this.swing = swing;
+ this.textWidth = textWidth;
+ this.textHeight = textHeight;
}
/**
* Re-query the text window size.
*/
public void queryWindowSize() {
- Insets insets = frame.getInsets();
- int height = frame.getHeight() - insets.top - insets.bottom;
- int width = frame.getWidth() - insets.left - insets.right;
+ Insets insets = swing.getInsets();
+ int width = swing.getWidth() - insets.left - insets.right;
+ int height = swing.getHeight() - insets.top - insets.bottom;
windowWidth = width / textWidth;
windowHeight = height / textHeight;
/*
System.err.printf("queryWindowSize(): frame %d %d window %d %d\n",
- frame.getWidth(), frame.getHeight(),
+ swing.getWidth(), swing.getHeight(),
windowWidth, windowHeight);
- */
+ */
}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin 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 Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer.backend;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.InputStream;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+
+import jexer.TKeypress;
+import jexer.bits.Cell;
+import jexer.bits.CellAttributes;
+import jexer.event.TCommandEvent;
+import jexer.event.TInputEvent;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
+import jexer.event.TResizeEvent;
+import static jexer.TCommand.*;
+import static jexer.TKeypress.*;
+
+/**
+ * This Screen backend reads keystrokes and mouse events and draws to either
+ * a Java Swing JFrame (potentially triple-buffered) or a JComponent.
+ *
+ * This class is a bit of an inversion of typical GUI classes. It performs
+ * all of the drawing logic from SwingTerminal (which is not a Swing class),
+ * 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 {
+
+ /**
+ * The Swing component or frame to draw to.
+ */
+ private SwingComponent swing;
+
+ // ------------------------------------------------------------------------
+ // Screen -----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Cursor style to draw.
+ */
+ public enum CursorStyle {
+ /**
+ * Use an underscore for the cursor.
+ */
+ UNDERLINE,
+
+ /**
+ * Use a solid block for the cursor.
+ */
+ BLOCK,
+
+ /**
+ * Use an outlined block for the cursor.
+ */
+ OUTLINE
+ }
+
+ /**
+ * A cache of previously-rendered glyphs for blinking text, when it is
+ * not visible.
+ */
+ private HashMap<Cell, BufferedImage> glyphCacheBlink;
+
+ /**
+ * A cache of previously-rendered glyphs for non-blinking, or
+ * blinking-and-visible, text.
+ */
+ private HashMap<Cell, BufferedImage> glyphCache;
+
+ // Colors to map DOS colors to AWT colors.
+ private static Color MYBLACK;
+ private static Color MYRED;
+ private static Color MYGREEN;
+ private static Color MYYELLOW;
+ private static Color MYBLUE;
+ private static Color MYMAGENTA;
+ private static Color MYCYAN;
+ private static Color MYWHITE;
+ private static Color MYBOLD_BLACK;
+ private static Color MYBOLD_RED;
+ private static Color MYBOLD_GREEN;
+ private static Color MYBOLD_YELLOW;
+ private static Color MYBOLD_BLUE;
+ private static Color MYBOLD_MAGENTA;
+ private static Color MYBOLD_CYAN;
+ private static Color MYBOLD_WHITE;
+
+ /**
+ * When true, all the MYBLACK, MYRED, etc. colors are set.
+ */
+ private static boolean dosColors = false;
+
+ /**
+ * Setup Swing colors to match DOS color palette.
+ */
+ private static void setDOSColors() {
+ if (dosColors) {
+ return;
+ }
+ MYBLACK = new Color(0x00, 0x00, 0x00);
+ MYRED = new Color(0xa8, 0x00, 0x00);
+ MYGREEN = new Color(0x00, 0xa8, 0x00);
+ MYYELLOW = new Color(0xa8, 0x54, 0x00);
+ MYBLUE = new Color(0x00, 0x00, 0xa8);
+ MYMAGENTA = new Color(0xa8, 0x00, 0xa8);
+ MYCYAN = new Color(0x00, 0xa8, 0xa8);
+ MYWHITE = new Color(0xa8, 0xa8, 0xa8);
+ MYBOLD_BLACK = new Color(0x54, 0x54, 0x54);
+ MYBOLD_RED = new Color(0xfc, 0x54, 0x54);
+ MYBOLD_GREEN = new Color(0x54, 0xfc, 0x54);
+ MYBOLD_YELLOW = new Color(0xfc, 0xfc, 0x54);
+ MYBOLD_BLUE = new Color(0x54, 0x54, 0xfc);
+ MYBOLD_MAGENTA = new Color(0xfc, 0x54, 0xfc);
+ MYBOLD_CYAN = new Color(0x54, 0xfc, 0xfc);
+ MYBOLD_WHITE = new Color(0xfc, 0xfc, 0xfc);
+
+ dosColors = true;
+ }
+
+ /**
+ * The terminus font resource filename.
+ */
+ private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
+
+ /**
+ * If true, we were successful getting Terminus.
+ */
+ private boolean gotTerminus = false;
+
+ /**
+ * If true, we were successful at getting the font dimensions.
+ */
+ private boolean gotFontDimensions = false;
+
+ /**
+ * The currently selected font.
+ */
+ private Font font = null;
+
+ /**
+ * The currently selected font size in points.
+ */
+ private int fontSize = 16;
+
+ /**
+ * Width of a character cell in pixels.
+ */
+ private int textWidth = 1;
+
+ /**
+ * Height of a character cell in pixels.
+ */
+ private int textHeight = 1;
+
+ /**
+ * Descent of a character cell in pixels.
+ */
+ private int maxDescent = 0;
+
+ /**
+ * System-dependent Y adjustment for text in the character cell.
+ */
+ private int textAdjustY = 0;
+
+ /**
+ * System-dependent X adjustment for text in the character cell.
+ */
+ private int textAdjustX = 0;
+
+ /**
+ * Top pixel absolute location.
+ */
+ private int top = 30;
+
+ /**
+ * Left pixel absolute location.
+ */
+ private int left = 30;
+
+ /**
+ * The cursor style to draw.
+ */
+ private CursorStyle cursorStyle = CursorStyle.UNDERLINE;
+
+ /**
+ * The number of millis to wait before switching the blink from
+ * visible to invisible.
+ */
+ private long blinkMillis = 500;
+
+ /**
+ * If true, the cursor should be visible right now based on the blink
+ * time.
+ */
+ private boolean cursorBlinkVisible = true;
+
+ /**
+ * The time that the blink last flipped from visible to invisible or
+ * from invisible to visible.
+ */
+ private long lastBlinkTime = 0;
+
+ /**
+ * Get the font size in points.
+ *
+ * @return font size in points
+ */
+ public int getFontSize() {
+ return fontSize;
+ }
+
+ /**
+ * Set the font size in points.
+ *
+ * @param fontSize font size in points
+ */
+ public void setFontSize(final int fontSize) {
+ this.fontSize = fontSize;
+ Font newFont = font.deriveFont((float) fontSize);
+ setFont(newFont);
+ }
+
+ /**
+ * Set to a new font, and resize the screen to match its dimensions.
+ *
+ * @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();
+ }
+
+ /**
+ * Set the font to Terminus, the best all-around font for both CP437 and
+ * ISO8859-1.
+ */
+ public void getDefaultFont() {
+ 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) {
+ e.printStackTrace();
+ font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
+ }
+
+ setFont(font);
+ }
+
+ /**
+ * Convert a CellAttributes foreground color to an Swing Color.
+ *
+ * @param attr the text attributes
+ * @return the Swing Color
+ */
+ private Color attrToForegroundColor(final CellAttributes attr) {
+ if (attr.isBold()) {
+ if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
+ return MYBOLD_BLACK;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
+ return MYBOLD_RED;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
+ return MYBOLD_BLUE;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
+ return MYBOLD_GREEN;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
+ return MYBOLD_YELLOW;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
+ return MYBOLD_CYAN;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
+ return MYBOLD_MAGENTA;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
+ return MYBOLD_WHITE;
+ }
+ } else {
+ if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
+ return MYBLACK;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
+ return MYRED;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
+ return MYBLUE;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
+ return MYGREEN;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
+ return MYYELLOW;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
+ return MYCYAN;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
+ return MYMAGENTA;
+ } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
+ return MYWHITE;
+ }
+ }
+ throw new IllegalArgumentException("Invalid color: " +
+ attr.getForeColor().getValue());
+ }
+
+ /**
+ * Convert a CellAttributes background color to an Swing Color.
+ *
+ * @param attr the text attributes
+ * @return the Swing Color
+ */
+ private Color attrToBackgroundColor(final CellAttributes attr) {
+ if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
+ return MYBLACK;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
+ return MYRED;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
+ return MYBLUE;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
+ return MYGREEN;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
+ return MYYELLOW;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
+ return MYCYAN;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
+ return MYMAGENTA;
+ } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
+ return MYWHITE;
+ }
+ throw new IllegalArgumentException("Invalid color: " +
+ attr.getBackColor().getValue());
+ }
+
+ /**
+ * 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
+ */
+ private boolean getFontAdjustments() {
+ BufferedImage image = null;
+
+ // What SHOULD happen is that the topmost/leftmost white pixel is at
+ // position (gr2x, gr2y). But it might also be off by a pixel in
+ // either direction.
+
+ Graphics2D gr2 = null;
+ int gr2x = 3;
+ int gr2y = 3;
+ image = new BufferedImage(textWidth * 2, textHeight * 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.setColor(java.awt.Color.WHITE);
+ char [] chars = new char[1];
+ chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR;
+ gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - 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();
+
+ 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) {
+ textAdjustX = (gr2x - x);
+
+ // System.err.println("textAdjustX: " + textAdjustX);
+ return true;
+ }
+ }
+ }
+
+ // Something weird happened, don't rely on this function.
+ // System.err.println("getFontAdjustments: false");
+ return false;
+ }
+
+ /**
+ * Figure out my font dimensions. This code path works OK for the JFrame
+ * case, and can be called immediately after JFrame creation.
+ */
+ private void getFontDimensions() {
+ swing.setFont(font);
+ Graphics gr = swing.getGraphics();
+ if (gr == null) {
+ return;
+ }
+ getFontDimensions(gr);
+ }
+
+ /**
+ * Figure out my font dimensions. This code path is needed to lazy-load
+ * the information inside paint().
+ *
+ * @param gr Graphics object to use
+ */
+ private void getFontDimensions(final Graphics gr) {
+ swing.setFont(font);
+ FontMetrics fm = gr.getFontMetrics();
+ 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;
+
+ // This produces the same number, but works better for ugly
+ // monospace.
+ textHeight = fm.getMaxAscent() + maxDescent - leading;
+
+ // TODO: is this still necessary?
+ 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;
+ }
+ }
+
+ if (sessionInfo != null) {
+ sessionInfo.setTextCellDimensions(textWidth, textHeight);
+ }
+ gotFontDimensions = true;
+ }
+
+ /**
+ * Resize to font dimensions.
+ */
+ public void resizeToScreen() {
+ swing.setDimensions(textWidth * width, textHeight * height);
+ }
+
+ /**
+ * Draw one glyph 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 drawGlyph(final Graphics gr, final Cell cell,
+ final int xPixel, final int yPixel) {
+
+ /*
+ System.err.println("drawGlyph(): " + xPixel + " " + yPixel +
+ " " + cell);
+ */
+
+ BufferedImage image = null;
+ if (cell.isBlink() && !cursorBlinkVisible) {
+ image = glyphCacheBlink.get(cell);
+ } else {
+ image = glyphCache.get(cell);
+ }
+ if (image != null) {
+ if (swing.getFrame() != null) {
+ gr.drawImage(image, xPixel, yPixel, swing.getFrame());
+ } else {
+ gr.drawImage(image, xPixel, yPixel, swing.getComponent());
+ }
+ return;
+ }
+
+ // Generate glyph and draw it.
+ Graphics2D gr2 = null;
+ int gr2x = xPixel;
+ int gr2y = yPixel;
+ if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
+ image = new BufferedImage(textWidth, textHeight,
+ BufferedImage.TYPE_INT_ARGB);
+ gr2 = image.createGraphics();
+ gr2.setFont(swing.getFont());
+ gr2x = 0;
+ gr2y = 0;
+ } else {
+ gr2 = (Graphics2D) gr;
+ }
+
+ Cell cellColor = new Cell();
+ cellColor.setTo(cell);
+
+ // Check for reverse
+ if (cell.isReverse()) {
+ cellColor.setForeColor(cell.getBackColor());
+ cellColor.setBackColor(cell.getForeColor());
+ }
+
+ // Draw the background rectangle, then the foreground character.
+ gr2.setColor(attrToBackgroundColor(cellColor));
+ gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
+
+ // Handle blink and underline
+ if (!cell.isBlink()
+ || (cell.isBlink() && cursorBlinkVisible)
+ ) {
+ gr2.setColor(attrToForegroundColor(cellColor));
+ char [] chars = new char[1];
+ chars[0] = cell.getChar();
+ gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
+ gr2y + textHeight - maxDescent + textAdjustY);
+
+ if (cell.isUnderline()) {
+ gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
+ }
+ }
+
+ if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
+ gr2.dispose();
+
+ // We need a new key that will not be mutated by
+ // invertCell().
+ Cell key = new Cell();
+ key.setTo(cell);
+ if (cell.isBlink() && !cursorBlinkVisible) {
+ glyphCacheBlink.put(key, image);
+ } else {
+ glyphCache.put(key, image);
+ }
+
+ if (swing.getFrame() != null) {
+ gr.drawImage(image, xPixel, yPixel, swing.getFrame());
+ } else {
+ gr.drawImage(image, xPixel, yPixel, swing.getComponent());
+ }
+ }
+
+ }
+
+ /**
+ * Check if the cursor is visible, and if so draw it.
+ *
+ * @param gr the Swing Graphics context
+ */
+ private void drawCursor(final Graphics gr) {
+
+ if (cursorVisible
+ && (cursorY <= height - 1)
+ && (cursorX <= width - 1)
+ && cursorBlinkVisible
+ ) {
+ int xPixel = cursorX * textWidth + left;
+ int yPixel = cursorY * textHeight + top;
+ Cell lCell = logical[cursorX][cursorY];
+ gr.setColor(attrToForegroundColor(lCell));
+ switch (cursorStyle) {
+ default:
+ // Fall through...
+ case UNDERLINE:
+ gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
+ break;
+ case BLOCK:
+ gr.fillRect(xPixel, yPixel, textWidth, textHeight);
+ break;
+ case OUTLINE:
+ gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Paint redraws the whole screen.
+ *
+ * @param gr the Swing Graphics context
+ */
+ public void paint(final Graphics gr) {
+
+ if (gotFontDimensions == false) {
+ // Lazy-load the text width/height
+ // System.err.println("calling getFontDimensions...");
+ getFontDimensions(gr);
+ /*
+ System.err.println("textWidth " + textWidth +
+ " textHeight " + textHeight);
+ System.err.println("FONT: " + swing.getFont() + " font " + font);
+ */
+ // resizeToScreen();
+ }
+
+ // See if it is time to flip the blink time.
+ long nowTime = (new Date()).getTime();
+ if (nowTime > blinkMillis + lastBlinkTime) {
+ lastBlinkTime = nowTime;
+ cursorBlinkVisible = !cursorBlinkVisible;
+ }
+
+ int xCellMin = 0;
+ int xCellMax = width;
+ int yCellMin = 0;
+ int yCellMax = height;
+
+ Rectangle bounds = gr.getClipBounds();
+ if (bounds != null) {
+ // Only update what is in the bounds
+ xCellMin = textColumn(bounds.x);
+ xCellMax = textColumn(bounds.x + bounds.width);
+ if (xCellMax > width) {
+ xCellMax = width;
+ }
+ if (xCellMin >= xCellMax) {
+ xCellMin = xCellMax - 2;
+ }
+ if (xCellMin < 0) {
+ xCellMin = 0;
+ }
+ yCellMin = textRow(bounds.y);
+ yCellMax = textRow(bounds.y + bounds.height);
+ if (yCellMax > height) {
+ yCellMax = height;
+ }
+ if (yCellMin >= yCellMax) {
+ yCellMin = yCellMax - 2;
+ }
+ if (yCellMin < 0) {
+ yCellMin = 0;
+ }
+ } else {
+ // We need a total repaint
+ reallyCleared = true;
+ }
+
+ // Prevent updates to the screen's data from the TApplication
+ // threads.
+ synchronized (this) {
+
+ /*
+ System.err.printf("bounds %s X %d %d Y %d %d\n",
+ bounds, xCellMin, xCellMax, yCellMin, yCellMax);
+ */
+
+ for (int y = yCellMin; y < yCellMax; y++) {
+ for (int x = xCellMin; x < xCellMax; x++) {
+
+ int xPixel = x * textWidth + left;
+ int yPixel = y * textHeight + top;
+
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
+
+ if (!lCell.equals(pCell)
+ || lCell.isBlink()
+ || reallyCleared
+ || (swing.getFrame() == null)) {
+
+ drawGlyph(gr, lCell, xPixel, yPixel);
+
+ // Physical is always updated
+ physical[x][y].setTo(lCell);
+ }
+ }
+ }
+ drawCursor(gr);
+
+ dirty = false;
+ reallyCleared = false;
+ } // synchronized (this)
+ }
+
+ /**
+ * Restore terminal to normal state.
+ */
+ public void shutdown() {
+ swing.dispose();
+ }
+
+ /**
+ * Push the logical screen to the physical device.
+ */
+ @Override
+ public void flushPhysical() {
+
+ /*
+ System.err.printf("flushPhysical(): reallyCleared %s dirty %s\n",
+ reallyCleared, dirty);
+ */
+
+ // If reallyCleared is set, we have to draw everything.
+ if ((swing.getFrame() != null)
+ && (swing.getBufferStrategy() != null)
+ && (reallyCleared == true)
+ ) {
+ // Triple-buffering: we have to redraw everything on this thread.
+ Graphics gr = swing.getBufferStrategy().getDrawGraphics();
+ swing.paint(gr);
+ gr.dispose();
+ swing.getBufferStrategy().show();
+ // sync() doesn't seem to help the tearing for me.
+ // Toolkit.getDefaultToolkit().sync();
+ return;
+ } else if (((swing.getFrame() != null)
+ && (swing.getBufferStrategy() == null))
+ || (reallyCleared == true)
+ ) {
+ // Repaint everything on the Swing thread.
+ // System.err.println("REPAINT ALL");
+ swing.repaint();
+ return;
+ }
+
+ // Do nothing if nothing happened.
+ if (!dirty) {
+ return;
+ }
+
+ if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
+ // See if it is time to flip the blink time.
+ long nowTime = (new Date()).getTime();
+ if (nowTime > blinkMillis + lastBlinkTime) {
+ lastBlinkTime = nowTime;
+ cursorBlinkVisible = !cursorBlinkVisible;
+ }
+
+ Graphics gr = swing.getBufferStrategy().getDrawGraphics();
+
+ synchronized (this) {
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
+
+ int xPixel = x * textWidth + left;
+ int yPixel = y * textHeight + top;
+
+ if (!lCell.equals(pCell)
+ || ((x == cursorX)
+ && (y == cursorY)
+ && cursorVisible)
+ || (lCell.isBlink())
+ ) {
+ drawGlyph(gr, lCell, xPixel, yPixel);
+ physical[x][y].setTo(lCell);
+ }
+ }
+ }
+ drawCursor(gr);
+ } // synchronized (this)
+
+ gr.dispose();
+ swing.getBufferStrategy().show();
+ // sync() doesn't seem to help the tearing for me.
+ // Toolkit.getDefaultToolkit().sync();
+ return;
+ }
+
+ // Swing thread version: request a repaint, but limit it to the area
+ // that has changed.
+
+ // Find the minimum-size damaged region.
+ int xMin = swing.getWidth();
+ int xMax = 0;
+ int yMin = swing.getHeight();
+ int yMax = 0;
+
+ synchronized (this) {
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
+
+ int xPixel = x * textWidth + left;
+ int yPixel = y * textHeight + top;
+
+ if (!lCell.equals(pCell)
+ || ((x == cursorX)
+ && (y == cursorY)
+ && cursorVisible)
+ || lCell.isBlink()
+ ) {
+ if (xPixel < xMin) {
+ xMin = xPixel;
+ }
+ if (xPixel + textWidth > xMax) {
+ xMax = xPixel + textWidth;
+ }
+ if (yPixel < yMin) {
+ yMin = yPixel;
+ }
+ if (yPixel + textHeight > yMax) {
+ yMax = yPixel + textHeight;
+ }
+ }
+ }
+ }
+ }
+ if (xMin + textWidth >= xMax) {
+ xMax += textWidth;
+ }
+ if (yMin + textHeight >= yMax) {
+ yMax += textHeight;
+ }
+
+ // Repaint the desired area
+ /*
+ System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
+ yMin, yMax);
+ */
+
+ if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
+ // This path should never be taken, but is left here for
+ // completeness.
+ Graphics gr = swing.getBufferStrategy().getDrawGraphics();
+ Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
+ yMax - yMin);
+ gr.setClip(bounds);
+ swing.paint(gr);
+ gr.dispose();
+ swing.getBufferStrategy().show();
+ // sync() doesn't seem to help the tearing for me.
+ // Toolkit.getDefaultToolkit().sync();
+ } else {
+ // Repaint on the Swing thread.
+ swing.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
+ }
+ }
+
+ /**
+ * Put the cursor at (x,y).
+ *
+ * @param visible if true, the cursor should be visible
+ * @param x column coordinate to put the cursor on
+ * @param y row coordinate to put the cursor on
+ */
+ @Override
+ public void putCursor(final boolean visible, final int x, final int y) {
+
+ if ((visible == cursorVisible) && ((x == cursorX) && (y == cursorY))) {
+ // See if it is time to flip the blink time.
+ long nowTime = (new Date()).getTime();
+ if (nowTime < blinkMillis + lastBlinkTime) {
+ // Nothing has changed, so don't do anything.
+ return;
+ }
+ }
+
+ if (cursorVisible
+ && (cursorY <= height - 1)
+ && (cursorX <= width - 1)
+ ) {
+ // Make the current cursor position dirty
+ if (physical[cursorX][cursorY].getChar() == 'Q') {
+ physical[cursorX][cursorY].setChar('X');
+ } else {
+ physical[cursorX][cursorY].setChar('Q');
+ }
+ }
+
+ super.putCursor(visible, x, y);
+ }
+
+ /**
+ * Convert pixel column position to text cell column position.
+ *
+ * @param x pixel column position
+ * @return text cell column position
+ */
+ public int textColumn(final int x) {
+ return ((x - left) / textWidth);
+ }
+
+ /**
+ * Convert pixel row position to text cell row position.
+ *
+ * @param y pixel row position
+ * @return text cell row position
+ */
+ public int textRow(final int y) {
+ return ((y - top) / textHeight);
+ }
+
+ /**
+ * Set the window title.
+ *
+ * @param title the new title
+ */
+ public void setTitle(final String title) {
+ swing.setTitle(title);
+ }
+
+ // ------------------------------------------------------------------------
+ // TerminalReader ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * The session information.
+ */
+ private SwingSessionInfo sessionInfo;
+
+ /**
+ * Getter for sessionInfo.
+ *
+ * @return the SessionInfo
+ */
+ public SessionInfo getSessionInfo() {
+ return sessionInfo;
+ }
+
+ /**
+ * The listening object that run() wakes up on new input.
+ */
+ private Object listener;
+
+ /**
+ * Set listener to a different Object.
+ *
+ * @param listener the new listening object that run() wakes up on new
+ * input
+ */
+ public void setListener(final Object listener) {
+ this.listener = listener;
+ }
+
+ /**
+ * The event queue, filled up by a thread reading on input.
+ */
+ private List<TInputEvent> eventQueue;
+
+ /**
+ * The last reported mouse X position.
+ */
+ private int oldMouseX = -1;
+
+ /**
+ * The last reported mouse Y position.
+ */
+ private int oldMouseY = -1;
+
+ /**
+ * true if mouse1 was down. Used to report mouse1 on the release event.
+ */
+ private boolean mouse1 = false;
+
+ /**
+ * true if mouse2 was down. Used to report mouse2 on the release event.
+ */
+ private boolean mouse2 = false;
+
+ /**
+ * true if mouse3 was down. Used to report mouse3 on the release event.
+ */
+ private boolean mouse3 = false;
+
+ /**
+ * Public constructor creates a new JFrame to render to.
+ *
+ * @param windowWidth the number of text columns to start with
+ * @param windowHeight the number of text rows to start with
+ * @param fontSize the size in points. Good values to pick are: 16, 20,
+ * 22, and 24.
+ * @param listener the object this backend needs to wake up when new
+ * input comes in
+ */
+ public SwingTerminal(final int windowWidth, final int windowHeight,
+ final int fontSize, final Object listener) {
+
+ 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("false")) {
+
+ SwingComponent.tripleBuffer = false;
+ }
+ }
+
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+
+ JFrame frame = new JFrame() {
+
+ /**
+ * Serializable version.
+ */
+ private static final long serialVersionUID = 1;
+
+ /**
+ * The code that performs the actual drawing.
+ */
+ public SwingTerminal screen = null;
+
+ /*
+ * Anonymous class initializer saves the screen
+ * reference, so that paint() and the like call out
+ * to SwingTerminal.
+ */
+ {
+ this.screen = SwingTerminal.this;
+ }
+
+ /**
+ * Update redraws the whole screen.
+ *
+ * @param gr the Swing Graphics context
+ */
+ @Override
+ public void update(final Graphics gr) {
+ // The default update clears the area. Don't do
+ // that, instead just paint it directly.
+ paint(gr);
+ }
+
+ /**
+ * Paint redraws the whole screen.
+ *
+ * @param gr the Swing Graphics context
+ */
+ @Override
+ public void paint(final Graphics gr) {
+ if (screen != null) {
+ screen.paint(gr);
+ }
+ }
+ };
+
+ // Get the Swing component
+ SwingTerminal.this.swing = new SwingComponent(frame);
+
+ // Hang onto top and left for drawing.
+ Insets insets = SwingTerminal.this.swing.getInsets();
+ SwingTerminal.this.left = insets.left;
+ SwingTerminal.this.top = insets.top;
+
+ // Load the font so that we can set sessionInfo.
+ getDefaultFont();
+
+ // Get the default cols x rows and set component size
+ // accordingly.
+ SwingTerminal.this.sessionInfo =
+ new SwingSessionInfo(SwingTerminal.this.swing,
+ SwingTerminal.this.textWidth,
+ SwingTerminal.this.textHeight);
+
+ SwingTerminal.this.setDimensions(sessionInfo.getWindowWidth(),
+ sessionInfo.getWindowHeight());
+
+ SwingTerminal.this.resizeToScreen();
+ SwingTerminal.this.swing.setVisible(true);
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ this.listener = listener;
+ mouse1 = false;
+ mouse2 = false;
+ mouse3 = false;
+ eventQueue = new LinkedList<TInputEvent>();
+
+ // Add listeners to Swing.
+ swing.addKeyListener(this);
+ swing.addWindowListener(this);
+ swing.addComponentListener(this);
+ swing.addMouseListener(this);
+ swing.addMouseMotionListener(this);
+ swing.addMouseWheelListener(this);
+ }
+
+ /**
+ * Public constructor creates a new JFrame to render to.
+ *
+ * @param component the Swing component to render to
+ * @param windowWidth the number of text columns to start with
+ * @param windowHeight the number of text rows to start with
+ * @param fontSize the size in points. Good values to pick are: 16, 20,
+ * 22, and 24.
+ * @param listener the object this backend needs to wake up when new
+ * input comes in
+ */
+ public SwingTerminal(final JComponent component, final int windowWidth,
+ final int windowHeight, final int fontSize, final Object listener) {
+
+ 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;
+ }
+
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+
+ JComponent newComponent = new JComponent() {
+
+ /**
+ * Serializable version.
+ */
+ private static final long serialVersionUID = 1;
+
+ /**
+ * The code that performs the actual drawing.
+ */
+ public SwingTerminal screen = null;
+
+ /*
+ * Anonymous class initializer saves the screen
+ * reference, so that paint() and the like call out
+ * to SwingTerminal.
+ */
+ {
+ this.screen = SwingTerminal.this;
+ }
+
+ /**
+ * Update redraws the whole screen.
+ *
+ * @param gr the Swing Graphics context
+ */
+ @Override
+ public void update(final Graphics gr) {
+ // The default update clears the area. Don't do
+ // that, instead just paint it directly.
+ paint(gr);
+ }
+
+ /**
+ * Paint redraws the whole screen.
+ *
+ * @param gr the Swing Graphics context
+ */
+ @Override
+ public void paint(final Graphics gr) {
+ if (screen != null) {
+ screen.paint(gr);
+ }
+ }
+ };
+ component.setLayout(new BorderLayout());
+ component.add(newComponent);
+
+ // Get the Swing component
+ SwingTerminal.this.swing = new SwingComponent(component);
+
+ // Hang onto top and left for drawing.
+ Insets insets = SwingTerminal.this.swing.getInsets();
+ SwingTerminal.this.left = insets.left;
+ SwingTerminal.this.top = insets.top;
+
+ // Load the font so that we can set sessionInfo.
+ getDefaultFont();
+
+ // Get the default cols x rows and set component size
+ // accordingly.
+ SwingTerminal.this.sessionInfo =
+ new SwingSessionInfo(SwingTerminal.this.swing,
+ SwingTerminal.this.textWidth,
+ SwingTerminal.this.textHeight);
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ this.listener = listener;
+ mouse1 = false;
+ mouse2 = false;
+ mouse3 = false;
+ eventQueue = new LinkedList<TInputEvent>();
+
+ // Add listeners to Swing.
+ swing.addKeyListener(this);
+ swing.addWindowListener(this);
+ swing.addComponentListener(this);
+ swing.addMouseListener(this);
+ swing.addMouseMotionListener(this);
+ swing.addMouseWheelListener(this);
+ }
+
+ /**
+ * Check if there are events in the queue.
+ *
+ * @return if true, getEvents() has something to return to the backend
+ */
+ public boolean hasEvents() {
+ synchronized (eventQueue) {
+ return (eventQueue.size() > 0);
+ }
+ }
+
+ /**
+ * Return any events in the IO queue.
+ *
+ * @param queue list to append new events to
+ */
+ public void getEvents(final List<TInputEvent> queue) {
+ synchronized (eventQueue) {
+ if (eventQueue.size() > 0) {
+ synchronized (queue) {
+ queue.addAll(eventQueue);
+ }
+ eventQueue.clear();
+ }
+ }
+ }
+
+ /**
+ * Restore terminal to normal state.
+ */
+ public void closeTerminal() {
+ shutdown();
+ }
+
+ /**
+ * Pass Swing keystrokes into the event queue.
+ *
+ * @param key keystroke received
+ */
+ public void keyReleased(final KeyEvent key) {
+ // Ignore release events
+ }
+
+ /**
+ * Pass Swing keystrokes into the event queue.
+ *
+ * @param key keystroke received
+ */
+ public void keyTyped(final KeyEvent key) {
+ // Ignore typed events
+ }
+
+ /**
+ * Pass Swing keystrokes into the event queue.
+ *
+ * @param key keystroke received
+ */
+ public void keyPressed(final KeyEvent key) {
+ boolean alt = false;
+ boolean shift = false;
+ boolean ctrl = false;
+ char ch = ' ';
+ boolean isKey = false;
+ if (key.isActionKey()) {
+ isKey = true;
+ } else {
+ ch = key.getKeyChar();
+ }
+ alt = key.isAltDown();
+ ctrl = key.isControlDown();
+ shift = key.isShiftDown();
+
+ /*
+ System.err.printf("Swing Key: %s\n", key);
+ System.err.printf(" isKey: %s\n", isKey);
+ System.err.printf(" alt: %s\n", alt);
+ System.err.printf(" ctrl: %s\n", ctrl);
+ System.err.printf(" shift: %s\n", shift);
+ System.err.printf(" ch: %s\n", ch);
+ */
+
+ // Special case: not return the bare modifier presses
+ switch (key.getKeyCode()) {
+ case KeyEvent.VK_ALT:
+ return;
+ case KeyEvent.VK_ALT_GRAPH:
+ return;
+ case KeyEvent.VK_CONTROL:
+ return;
+ case KeyEvent.VK_SHIFT:
+ return;
+ case KeyEvent.VK_META:
+ return;
+ default:
+ break;
+ }
+
+ TKeypress keypress = null;
+ if (isKey) {
+ switch (key.getKeyCode()) {
+ case KeyEvent.VK_F1:
+ keypress = new TKeypress(true, TKeypress.F1, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F2:
+ keypress = new TKeypress(true, TKeypress.F2, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F3:
+ keypress = new TKeypress(true, TKeypress.F3, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F4:
+ keypress = new TKeypress(true, TKeypress.F4, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F5:
+ keypress = new TKeypress(true, TKeypress.F5, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F6:
+ keypress = new TKeypress(true, TKeypress.F6, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F7:
+ keypress = new TKeypress(true, TKeypress.F7, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F8:
+ keypress = new TKeypress(true, TKeypress.F8, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F9:
+ keypress = new TKeypress(true, TKeypress.F9, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F10:
+ keypress = new TKeypress(true, TKeypress.F10, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F11:
+ keypress = new TKeypress(true, TKeypress.F11, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_F12:
+ keypress = new TKeypress(true, TKeypress.F12, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_HOME:
+ keypress = new TKeypress(true, TKeypress.HOME, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_END:
+ keypress = new TKeypress(true, TKeypress.END, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_PAGE_UP:
+ keypress = new TKeypress(true, TKeypress.PGUP, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_PAGE_DOWN:
+ keypress = new TKeypress(true, TKeypress.PGDN, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_INSERT:
+ keypress = new TKeypress(true, TKeypress.INS, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_DELETE:
+ keypress = new TKeypress(true, TKeypress.DEL, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_RIGHT:
+ keypress = new TKeypress(true, TKeypress.RIGHT, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_LEFT:
+ keypress = new TKeypress(true, TKeypress.LEFT, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_UP:
+ keypress = new TKeypress(true, TKeypress.UP, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_DOWN:
+ keypress = new TKeypress(true, TKeypress.DOWN, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_TAB:
+ // Special case: distinguish TAB vs BTAB
+ if (shift) {
+ keypress = kbShiftTab;
+ } else {
+ keypress = kbTab;
+ }
+ break;
+ case KeyEvent.VK_ENTER:
+ keypress = new TKeypress(true, TKeypress.ENTER, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_ESCAPE:
+ keypress = new TKeypress(true, TKeypress.ESC, ' ',
+ alt, ctrl, shift);
+ break;
+ case KeyEvent.VK_BACK_SPACE:
+ // Special case: return it as kbBackspace (Ctrl-H)
+ keypress = new TKeypress(false, 0, 'H', false, true, false);
+ break;
+ default:
+ // Unsupported, ignore
+ return;
+ }
+ }
+
+ if (keypress == null) {
+ switch (ch) {
+ case 0x08:
+ keypress = kbBackspace;
+ break;
+ case 0x0A:
+ keypress = kbEnter;
+ break;
+ case 0x1B:
+ keypress = kbEsc;
+ break;
+ case 0x0D:
+ keypress = kbEnter;
+ break;
+ case 0x09:
+ if (shift) {
+ keypress = kbShiftTab;
+ } else {
+ keypress = kbTab;
+ }
+ break;
+ case 0x7F:
+ keypress = kbDel;
+ break;
+ default:
+ if (!alt && ctrl && !shift) {
+ ch = KeyEvent.getKeyText(key.getKeyCode()).charAt(0);
+ }
+ // Not a special key, put it together
+ keypress = new TKeypress(false, 0, ch, alt, ctrl, shift);
+ }
+ }
+
+ // Save it and we are done.
+ synchronized (eventQueue) {
+ eventQueue.add(new TKeypressEvent(keypress));
+ }
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowActivated(final WindowEvent event) {
+ // Force a total repaint
+ synchronized (this) {
+ clearPhysical();
+ }
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowClosed(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowClosing(final WindowEvent event) {
+ // Drop a cmAbort and walk away
+ synchronized (eventQueue) {
+ eventQueue.add(new TCommandEvent(cmAbort));
+ }
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowDeactivated(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowDeiconified(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowIconified(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowOpened(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass component events into the event queue.
+ *
+ * @param event component event received
+ */
+ public void componentHidden(final ComponentEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass component events into the event queue.
+ *
+ * @param event component event received
+ */
+ public void componentShown(final ComponentEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass component events into the event queue.
+ *
+ * @param event component event received
+ */
+ public void componentMoved(final ComponentEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass component events into the event queue.
+ *
+ * @param event component event received
+ */
+ public void componentResized(final ComponentEvent event) {
+ if (gotFontDimensions == false) {
+ // We are still waiting to get font information. Don't pass a
+ // resize event up.
+ // System.err.println("size " + swing.getComponent().getSize());
+ return;
+ }
+
+ // Drop a new TResizeEvent into the queue
+ sessionInfo.queryWindowSize();
+ synchronized (eventQueue) {
+ TResizeEvent windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
+ sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
+ eventQueue.add(windowResize);
+ }
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+
+ /**
+ * Pass mouse events into the event queue.
+ *
+ * @param mouse mouse event received
+ */
+ public void mouseDragged(final MouseEvent mouse) {
+ int modifiers = mouse.getModifiersEx();
+ boolean eventMouse1 = false;
+ boolean eventMouse2 = false;
+ boolean eventMouse3 = false;
+ if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
+ eventMouse1 = true;
+ }
+ if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
+ eventMouse2 = true;
+ }
+ if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
+ eventMouse3 = true;
+ }
+ mouse1 = eventMouse1;
+ mouse2 = eventMouse2;
+ mouse3 = eventMouse3;
+ int x = textColumn(mouse.getX());
+ int y = textRow(mouse.getY());
+
+ TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
+ x, y, x, y, mouse1, mouse2, mouse3, false, false);
+
+ synchronized (eventQueue) {
+ eventQueue.add(mouseEvent);
+ }
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+
+ /**
+ * Pass mouse events into the event queue.
+ *
+ * @param mouse mouse event received
+ */
+ public void mouseMoved(final MouseEvent mouse) {
+ int x = textColumn(mouse.getX());
+ int y = textRow(mouse.getY());
+ if ((x == oldMouseX) && (y == oldMouseY)) {
+ // Bail out, we've moved some pixels but not a whole text cell.
+ return;
+ }
+ oldMouseX = x;
+ oldMouseY = y;
+
+ TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
+ x, y, x, y, mouse1, mouse2, mouse3, false, false);
+
+ synchronized (eventQueue) {
+ eventQueue.add(mouseEvent);
+ }
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+
+ /**
+ * Pass mouse events into the event queue.
+ *
+ * @param mouse mouse event received
+ */
+ public void mouseClicked(final MouseEvent mouse) {
+ // Ignore
+ }
+
+ /**
+ * Pass mouse events into the event queue.
+ *
+ * @param mouse mouse event received
+ */
+ public void mouseEntered(final MouseEvent mouse) {
+ // Ignore
+ }
+
+ /**
+ * Pass mouse events into the event queue.
+ *
+ * @param mouse mouse event received
+ */
+ public void mouseExited(final MouseEvent mouse) {
+ // Ignore
+ }
+
+ /**
+ * Pass mouse events into the event queue.
+ *
+ * @param mouse mouse event received
+ */
+ public void mousePressed(final MouseEvent mouse) {
+ int modifiers = mouse.getModifiersEx();
+ boolean eventMouse1 = false;
+ boolean eventMouse2 = false;
+ boolean eventMouse3 = false;
+ if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
+ eventMouse1 = true;
+ }
+ if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
+ eventMouse2 = true;
+ }
+ if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
+ eventMouse3 = true;
+ }
+ mouse1 = eventMouse1;
+ mouse2 = eventMouse2;
+ mouse3 = eventMouse3;
+ int x = textColumn(mouse.getX());
+ int y = textRow(mouse.getY());
+
+ TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
+ x, y, x, y, mouse1, mouse2, mouse3, false, false);
+
+ synchronized (eventQueue) {
+ eventQueue.add(mouseEvent);
+ }
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+
+ /**
+ * Pass mouse events into the event queue.
+ *
+ * @param mouse mouse event received
+ */
+ public void mouseReleased(final MouseEvent mouse) {
+ int modifiers = mouse.getModifiersEx();
+ boolean eventMouse1 = false;
+ boolean eventMouse2 = false;
+ boolean eventMouse3 = false;
+ if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
+ eventMouse1 = true;
+ }
+ if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
+ eventMouse2 = true;
+ }
+ if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
+ eventMouse3 = true;
+ }
+ if (mouse1) {
+ mouse1 = false;
+ eventMouse1 = true;
+ }
+ if (mouse2) {
+ mouse2 = false;
+ eventMouse2 = true;
+ }
+ if (mouse3) {
+ mouse3 = false;
+ eventMouse3 = true;
+ }
+ int x = textColumn(mouse.getX());
+ int y = textRow(mouse.getY());
+
+ TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP,
+ x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false);
+
+ synchronized (eventQueue) {
+ eventQueue.add(mouseEvent);
+ }
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+
+ /**
+ * Pass mouse events into the event queue.
+ *
+ * @param mouse mouse event received
+ */
+ public void mouseWheelMoved(final MouseWheelEvent mouse) {
+ int modifiers = mouse.getModifiersEx();
+ boolean eventMouse1 = false;
+ boolean eventMouse2 = false;
+ boolean eventMouse3 = false;
+ boolean mouseWheelUp = false;
+ boolean mouseWheelDown = false;
+ if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
+ eventMouse1 = true;
+ }
+ if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
+ eventMouse2 = true;
+ }
+ if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
+ eventMouse3 = true;
+ }
+ mouse1 = eventMouse1;
+ mouse2 = eventMouse2;
+ mouse3 = eventMouse3;
+ int x = textColumn(mouse.getX());
+ int y = textRow(mouse.getY());
+ if (mouse.getWheelRotation() > 0) {
+ mouseWheelDown = true;
+ }
+ if (mouse.getWheelRotation() < 0) {
+ mouseWheelUp = true;
+ }
+
+ TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
+ x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown);
+
+ synchronized (eventQueue) {
+ eventQueue.add(mouseEvent);
+ }
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+
+}
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
-package jexer.session;
+package jexer.backend;
/**
* TSessionInfo provides a default session implementation. The username is
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
-package jexer.session;
+package jexer.backend;
import java.io.BufferedReader;
import java.io.InputStreamReader;
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
+package jexer.backend;
+
+import java.util.List;
+
+import jexer.event.TInputEvent;
/**
- * Text terminal session values: width, height, username, language, etc.
+ * TerminalReader provides keyboard and mouse events.
*/
-package jexer.session;
+public interface TerminalReader {
+
+ /**
+ * Check if there are events in the queue.
+ *
+ * @return if true, getEvents() has something to return to the backend
+ */
+ public boolean hasEvents();
+
+ /**
+ * Classes must provide an implementation to get keyboard, mouse, and
+ * screen resize events.
+ *
+ * @param queue list to append new events to
+ */
+ public void getEvents(List<TInputEvent> queue);
+
+ /**
+ * Classes must provide an implementation that closes sockets, restores
+ * console, etc.
+ */
+ public void closeTerminal();
+
+}
--- /dev/null
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin 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 Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer.demos;
+
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+
+import jexer.backend.SwingBackend;
+
+/**
+ * This class is the main driver for a simple demonstration of Jexer's
+ * capabilities. It shows two Swing demo applications running in the same
+ * Swing UI.
+ */
+public class Demo5 implements WindowListener {
+
+ /**
+ * The first demo application instance.
+ */
+ DemoApplication app1 = null;
+
+ /**
+ * The second demo application instance.
+ */
+ DemoApplication app2 = null;
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowActivated(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowClosed(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowClosing(final WindowEvent event) {
+ if (app1 != null) {
+ app1.exit();
+ }
+ if (app2 != null) {
+ app2.exit();
+ }
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowDeactivated(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowDeiconified(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowIconified(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Pass window events into the event queue.
+ *
+ * @param event window event received
+ */
+ public void windowOpened(final WindowEvent event) {
+ // Ignore
+ }
+
+ /**
+ * Run two demo applications in separate panes.
+ */
+ private void addApplications() {
+
+ // Spin up the frame
+ JFrame frame = new JFrame();
+ frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ frame.addWindowListener(this);
+
+ // Create two panels with two applications, each with a different
+ // font size.
+ JPanel app1Panel = new JPanel();
+ SwingBackend app1Backend = new SwingBackend(app1Panel, new Object(),
+ 80, 25, 16);
+ app1 = new DemoApplication(app1Backend);
+ app1Backend.setListener(app1);
+
+ JPanel app2Panel = new JPanel();
+ SwingBackend app2Backend = new SwingBackend(app2Panel, new Object(),
+ 80, 25, 18);
+ app2 = new DemoApplication(app2Backend);
+ app1Backend.setListener(app2);
+ (new Thread(app1)).start();
+ (new Thread(app2)).start();
+
+ JSplitPane mainPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
+ app1Panel, app2Panel);
+ mainPane.setOneTouchExpandable(true);
+ mainPane.setDividerLocation(500);
+ mainPane.setDividerSize(6);
+ mainPane.setBorder(null);
+ frame.setContentPane(mainPane);
+
+ frame.setTitle("Two Jexer Apps In One Swing UI");
+ frame.setSize(1000, 640);
+ frame.setVisible(true);
+ }
+
+ /**
+ * Main entry point.
+ *
+ * @param args Command line arguments
+ */
+ public static void main(final String [] args) {
+ try {
+ Demo5 demo = new Demo5();
+ demo.addApplications();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+}
import jexer.*;
import jexer.event.*;
import jexer.menu.*;
+import jexer.backend.Backend;
+import jexer.backend.SwingTerminal;
/**
* The demo application itself.
item.setEnabled(false);
item = subMenu.addItem(2002, "&Normal (sub)");
+ if (getScreen() instanceof SwingTerminal) {
+ TMenu swingMenu = addMenu("&Swing");
+ item = swingMenu.addItem(3000, "&Bigger +2");
+ item = swingMenu.addItem(3001, "&Smaller -2");
+ }
+
addWindowMenu();
addHelpMenu();
}
this(input, reader, writer, false);
}
+ /**
+ * Public constructor.
+ *
+ * @param backend a Backend that is already ready to go.
+ */
+ public DemoApplication(final Backend backend) {
+ super(backend);
+
+ addAllWidgets();
+ }
+
/**
* Handle menu events.
*
@Override
public boolean onMenu(final TMenuEvent menu) {
+ if (menu.getId() == 3000) {
+ // Bigger +2
+ assert (getScreen() instanceof SwingTerminal);
+ SwingTerminal terminal = (SwingTerminal) getScreen();
+ terminal.setFontSize(terminal.getFontSize() + 2);
+ return true;
+ }
+ if (menu.getId() == 3001) {
+ // Smaller -2
+ assert (getScreen() instanceof SwingTerminal);
+ SwingTerminal terminal = (SwingTerminal) getScreen();
+ terminal.setFontSize(terminal.getFontSize() - 2);
+ return true;
+ }
+
if (menu.getId() == 2050) {
new TEditColorThemeWindow(this);
return true;
+++ /dev/null
-/*
- * Jexer - Java Text User Interface
- *
- * The MIT License (MIT)
- *
- * Copyright (C) 2017 Kevin 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 Kevin Lamonte [kevin.lamonte@gmail.com]
- * @version 1
- */
-package jexer.io;
-
-import jexer.bits.Cell;
-import jexer.bits.CellAttributes;
-
-/**
- * This Screen implementation draws to an xterm/ANSI X3.64/ECMA-48 type
- * terminal.
- */
-public final class ECMA48Screen extends Screen {
-
- /**
- * Emit debugging to stderr.
- */
- private boolean debugToStderr = false;
-
- /**
- * We call terminal.cursor() so need the instance.
- */
- private ECMA48Terminal terminal;
-
- /**
- * Public constructor.
- *
- * @param terminal ECMA48Terminal to use
- */
- public ECMA48Screen(final ECMA48Terminal terminal) {
- this.terminal = terminal;
-
- // Query the screen size
- setDimensions(terminal.getSessionInfo().getWindowWidth(),
- terminal.getSessionInfo().getWindowHeight());
- }
-
- /**
- * Perform a somewhat-optimal rendering of a line.
- *
- * @param y row coordinate. 0 is the top-most row.
- * @param sb StringBuilder to write escape sequences to
- * @param lastAttr cell attributes from the last call to flushLine
- */
- private void flushLine(final int y, final StringBuilder sb,
- CellAttributes lastAttr) {
-
- int lastX = -1;
- int textEnd = 0;
- for (int x = 0; x < width; x++) {
- Cell lCell = logical[x][y];
- if (!lCell.isBlank()) {
- textEnd = x;
- }
- }
- // Push textEnd to first column beyond the text area
- textEnd++;
-
- // DEBUG
- // reallyCleared = true;
-
- for (int x = 0; x < width; x++) {
- Cell lCell = logical[x][y];
- Cell pCell = physical[x][y];
-
- if (!lCell.equals(pCell) || reallyCleared) {
-
- if (debugToStderr) {
- System.err.printf("\n--\n");
- System.err.printf(" Y: %d X: %d\n", y, x);
- System.err.printf(" lCell: %s\n", lCell);
- System.err.printf(" pCell: %s\n", pCell);
- System.err.printf(" ==== \n");
- }
-
- if (lastAttr == null) {
- lastAttr = new CellAttributes();
- sb.append(terminal.normal());
- }
-
- // Place the cell
- if ((lastX != (x - 1)) || (lastX == -1)) {
- // Advancing at least one cell, or the first gotoXY
- sb.append(terminal.gotoXY(x, y));
- }
-
- assert (lastAttr != null);
-
- if ((x == textEnd) && (textEnd < width - 1)) {
- assert (lCell.isBlank());
-
- for (int i = x; i < width; i++) {
- assert (logical[i][y].isBlank());
- // Physical is always updated
- physical[i][y].reset();
- }
-
- // Clear remaining line
- sb.append(terminal.clearRemainingLine());
- lastAttr.reset();
- return;
- }
-
- // Now emit only the modified attributes
- if ((lCell.getForeColor() != lastAttr.getForeColor())
- && (lCell.getBackColor() != lastAttr.getBackColor())
- && (lCell.isBold() == lastAttr.isBold())
- && (lCell.isReverse() == lastAttr.isReverse())
- && (lCell.isUnderline() == lastAttr.isUnderline())
- && (lCell.isBlink() == lastAttr.isBlink())
- ) {
- // Both colors changed, attributes the same
- sb.append(terminal.color(lCell.isBold(),
- lCell.getForeColor(), lCell.getBackColor()));
-
- if (debugToStderr) {
- System.err.printf("1 Change only fore/back colors\n");
- }
- } else if ((lCell.getForeColor() != lastAttr.getForeColor())
- && (lCell.getBackColor() != lastAttr.getBackColor())
- && (lCell.isBold() != lastAttr.isBold())
- && (lCell.isReverse() != lastAttr.isReverse())
- && (lCell.isUnderline() != lastAttr.isUnderline())
- && (lCell.isBlink() != lastAttr.isBlink())
- ) {
- // Everything is different
- sb.append(terminal.color(lCell.getForeColor(),
- lCell.getBackColor(),
- lCell.isBold(), lCell.isReverse(),
- lCell.isBlink(),
- lCell.isUnderline()));
-
- if (debugToStderr) {
- System.err.printf("2 Set all attributes\n");
- }
- } else if ((lCell.getForeColor() != lastAttr.getForeColor())
- && (lCell.getBackColor() == lastAttr.getBackColor())
- && (lCell.isBold() == lastAttr.isBold())
- && (lCell.isReverse() == lastAttr.isReverse())
- && (lCell.isUnderline() == lastAttr.isUnderline())
- && (lCell.isBlink() == lastAttr.isBlink())
- ) {
-
- // Attributes same, foreColor different
- sb.append(terminal.color(lCell.isBold(),
- lCell.getForeColor(), true));
-
- if (debugToStderr) {
- System.err.printf("3 Change foreColor\n");
- }
- } else if ((lCell.getForeColor() == lastAttr.getForeColor())
- && (lCell.getBackColor() != lastAttr.getBackColor())
- && (lCell.isBold() == lastAttr.isBold())
- && (lCell.isReverse() == lastAttr.isReverse())
- && (lCell.isUnderline() == lastAttr.isUnderline())
- && (lCell.isBlink() == lastAttr.isBlink())
- ) {
- // Attributes same, backColor different
- sb.append(terminal.color(lCell.isBold(),
- lCell.getBackColor(), false));
-
- if (debugToStderr) {
- System.err.printf("4 Change backColor\n");
- }
- } else if ((lCell.getForeColor() == lastAttr.getForeColor())
- && (lCell.getBackColor() == lastAttr.getBackColor())
- && (lCell.isBold() == lastAttr.isBold())
- && (lCell.isReverse() == lastAttr.isReverse())
- && (lCell.isUnderline() == lastAttr.isUnderline())
- && (lCell.isBlink() == lastAttr.isBlink())
- ) {
-
- // All attributes the same, just print the char
- // NOP
-
- if (debugToStderr) {
- System.err.printf("5 Only emit character\n");
- }
- } else {
- // Just reset everything again
- sb.append(terminal.color(lCell.getForeColor(),
- lCell.getBackColor(),
- lCell.isBold(),
- lCell.isReverse(),
- lCell.isBlink(),
- lCell.isUnderline()));
-
- if (debugToStderr) {
- System.err.printf("6 Change all attributes\n");
- }
- }
- // Emit the character
- sb.append(lCell.getChar());
-
- // Save the last rendered cell
- lastX = x;
- lastAttr.setTo(lCell);
-
- // Physical is always updated
- physical[x][y].setTo(lCell);
-
- } // if (!lCell.equals(pCell) || (reallyCleared == true))
-
- } // for (int x = 0; x < width; x++)
- }
-
- /**
- * Render the screen to a string that can be emitted to something that
- * knows how to process ECMA-48/ANSI X3.64 escape sequences.
- *
- * @return escape sequences string that provides the updates to the
- * physical screen
- */
- private String flushString() {
- if (!dirty) {
- assert (!reallyCleared);
- return "";
- }
-
- CellAttributes attr = null;
-
- StringBuilder sb = new StringBuilder();
- if (reallyCleared) {
- attr = new CellAttributes();
- sb.append(terminal.clearAll());
- }
-
- for (int y = 0; y < height; y++) {
- flushLine(y, sb, attr);
- }
-
- dirty = false;
- reallyCleared = false;
-
- String result = sb.toString();
- if (debugToStderr) {
- System.err.printf("flushString(): %s\n", result);
- }
- return result;
- }
-
- /**
- * Push the logical screen to the physical device.
- */
- @Override
- public void flushPhysical() {
- String result = flushString();
- if ((cursorVisible)
- && (cursorY <= height - 1)
- && (cursorX <= width - 1)
- ) {
- result += terminal.cursor(true);
- result += terminal.gotoXY(cursorX, cursorY);
- } else {
- result += terminal.cursor(false);
- }
- terminal.getOutput().write(result);
- terminal.flush();
- }
-
- /**
- * Set the window title.
- *
- * @param title the new title
- */
- public void setTitle(final String title) {
- terminal.getOutput().write(terminal.setTitle(title));
- terminal.flush();
- }
-
-}
+++ /dev/null
-/*
- * Jexer - Java Text User Interface
- *
- * The MIT License (MIT)
- *
- * Copyright (C) 2017 Kevin 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 Kevin Lamonte [kevin.lamonte@gmail.com]
- * @version 1
- */
-package jexer.io;
-
-import java.awt.Color;
-import java.awt.Cursor;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Insets;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.Toolkit;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
-import java.awt.image.BufferStrategy;
-import java.io.InputStream;
-import java.util.Date;
-import java.util.HashMap;
-import javax.swing.JFrame;
-import javax.swing.SwingUtilities;
-
-import jexer.bits.Cell;
-import jexer.bits.CellAttributes;
-import jexer.session.SwingSessionInfo;
-
-/**
- * This Screen implementation draws to a Java Swing JFrame.
- */
-public final class SwingScreen extends Screen {
-
- /**
- * If true, use triple buffering thread.
- */
- private static boolean tripleBuffer = true;
-
- /**
- * Cursor style to draw.
- */
- public enum CursorStyle {
- /**
- * Use an underscore for the cursor.
- */
- UNDERLINE,
-
- /**
- * Use a solid block for the cursor.
- */
- BLOCK,
-
- /**
- * Use an outlined block for the cursor.
- */
- OUTLINE
- }
-
- private static Color MYBLACK;
- private static Color MYRED;
- private static Color MYGREEN;
- private static Color MYYELLOW;
- private static Color MYBLUE;
- private static Color MYMAGENTA;
- private static Color MYCYAN;
- private static Color MYWHITE;
-
- private static Color MYBOLD_BLACK;
- private static Color MYBOLD_RED;
- private static Color MYBOLD_GREEN;
- private static Color MYBOLD_YELLOW;
- private static Color MYBOLD_BLUE;
- private static Color MYBOLD_MAGENTA;
- private static Color MYBOLD_CYAN;
- private static Color MYBOLD_WHITE;
-
- private static boolean dosColors = false;
-
- /**
- * Setup Swing colors to match DOS color palette.
- */
- private static void setDOSColors() {
- if (dosColors) {
- return;
- }
- MYBLACK = new Color(0x00, 0x00, 0x00);
- MYRED = new Color(0xa8, 0x00, 0x00);
- MYGREEN = new Color(0x00, 0xa8, 0x00);
- MYYELLOW = new Color(0xa8, 0x54, 0x00);
- MYBLUE = new Color(0x00, 0x00, 0xa8);
- MYMAGENTA = new Color(0xa8, 0x00, 0xa8);
- MYCYAN = new Color(0x00, 0xa8, 0xa8);
- MYWHITE = new Color(0xa8, 0xa8, 0xa8);
- MYBOLD_BLACK = new Color(0x54, 0x54, 0x54);
- MYBOLD_RED = new Color(0xfc, 0x54, 0x54);
- MYBOLD_GREEN = new Color(0x54, 0xfc, 0x54);
- MYBOLD_YELLOW = new Color(0xfc, 0xfc, 0x54);
- MYBOLD_BLUE = new Color(0x54, 0x54, 0xfc);
- MYBOLD_MAGENTA = new Color(0xfc, 0x54, 0xfc);
- MYBOLD_CYAN = new Color(0x54, 0xfc, 0xfc);
- MYBOLD_WHITE = new Color(0xfc, 0xfc, 0xfc);
-
- dosColors = true;
- }
-
- /**
- * SwingFrame is our top-level hook into the Swing system.
- */
- class SwingFrame extends JFrame {
-
- /**
- * Serializable version.
- */
- private static final long serialVersionUID = 1;
-
- /**
- * The terminus font resource filename.
- */
- private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
-
- /**
- * The BufferStrategy object needed for triple-buffering.
- */
- private BufferStrategy bufferStrategy;
-
- /**
- * A cache of previously-rendered glyphs for blinking text, when it
- * is not visible.
- */
- private HashMap<Cell, BufferedImage> glyphCacheBlink;
-
- /**
- * A cache of previously-rendered glyphs for non-blinking, or
- * blinking-and-visible, text.
- */
- private HashMap<Cell, BufferedImage> glyphCache;
-
- /**
- * The TUI Screen data.
- */
- SwingScreen screen;
-
- /**
- * If true, we were successful getting Terminus.
- */
- private boolean gotTerminus = false;
-
- /**
- * Width of a character cell.
- */
- private int textWidth = 1;
-
- /**
- * Height of a character cell.
- */
- private int textHeight = 1;
-
- /**
- * Descent of a character cell.
- */
- private int maxDescent = 0;
-
- /**
- * System-dependent Y adjustment for text in the character cell.
- */
- private int textAdjustY = 0;
-
- /**
- * System-dependent X adjustment for text in the character cell.
- */
- private int textAdjustX = 0;
-
- /**
- * Top pixel absolute location.
- */
- private int top = 30;
-
- /**
- * Left pixel absolute location.
- */
- private int left = 30;
-
- /**
- * The cursor style to draw.
- */
- private CursorStyle cursorStyle = CursorStyle.UNDERLINE;
-
- /**
- * The number of millis to wait before switching the blink from
- * visible to invisible.
- */
- private long blinkMillis = 500;
-
- /**
- * If true, the cursor should be visible right now based on the blink
- * time.
- */
- private boolean cursorBlinkVisible = true;
-
- /**
- * The time that the blink last flipped from visible to invisible or
- * from invisible to visible.
- */
- private long lastBlinkTime = 0;
-
- /**
- * Convert a CellAttributes foreground color to an Swing Color.
- *
- * @param attr the text attributes
- * @return the Swing Color
- */
- private Color attrToForegroundColor(final CellAttributes attr) {
- if (attr.isBold()) {
- if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
- return MYBOLD_BLACK;
- } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
- return MYBOLD_RED;
- } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
- return MYBOLD_BLUE;
- } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
- return MYBOLD_GREEN;
- } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
- return MYBOLD_YELLOW;
- } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
- return MYBOLD_CYAN;
- } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
- return MYBOLD_MAGENTA;
- } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
- return MYBOLD_WHITE;
- }
- } else {
- if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
- return MYBLACK;
- } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
- return MYRED;
- } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
- return MYBLUE;
- } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
- return MYGREEN;
- } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
- return MYYELLOW;
- } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
- return MYCYAN;
- } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
- return MYMAGENTA;
- } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
- return MYWHITE;
- }
- }
- throw new IllegalArgumentException("Invalid color: " +
- attr.getForeColor().getValue());
- }
-
- /**
- * Convert a CellAttributes background color to an Swing Color.
- *
- * @param attr the text attributes
- * @return the Swing Color
- */
- private Color attrToBackgroundColor(final CellAttributes attr) {
- if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
- return MYBLACK;
- } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
- return MYRED;
- } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
- return MYBLUE;
- } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
- return MYGREEN;
- } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
- return MYYELLOW;
- } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
- return MYCYAN;
- } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
- return MYMAGENTA;
- } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
- return MYWHITE;
- }
- throw new IllegalArgumentException("Invalid color: " +
- attr.getBackColor().getValue());
- }
-
- /**
- * Public constructor.
- *
- * @param screen the Screen that Backend talks to
- * @param fontSize the size in points. Good values to pick are: 16,
- * 20, 22, and 24.
- */
- public SwingFrame(final SwingScreen screen, final int fontSize) {
- this.screen = screen;
- 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;
- }
-
- if (System.getProperty("jexer.Swing.tripleBuffer") != null) {
- if (System.getProperty("jexer.Swing.tripleBuffer").
- equals("false")) {
-
- SwingScreen.tripleBuffer = false;
- }
- }
-
- setTitle("Jexer Application");
- setBackground(Color.black);
-
- try {
- // Always try to use Terminus, the one decent font.
- 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);
- setFont(terminus);
- gotTerminus = true;
- } catch (Exception e) {
- e.printStackTrace();
- // setFont(new Font("Liberation Mono", Font.PLAIN, 24));
- setFont(new Font(Font.MONOSPACED, Font.PLAIN, fontSize));
- }
- pack();
-
- // Kill the X11 cursor
- // Transparent 16 x 16 pixel cursor image.
- BufferedImage cursorImg = new BufferedImage(16, 16,
- BufferedImage.TYPE_INT_ARGB);
- // Create a new blank cursor.
- Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
- cursorImg, new Point(0, 0), "blank cursor");
- setCursor(blankCursor);
-
- // Be capable of seeing Tab / Shift-Tab
- setFocusTraversalKeysEnabled(false);
-
- // Save the text cell width/height
- getFontDimensions();
-
- // Cache glyphs as they are rendered
- glyphCacheBlink = new HashMap<Cell, BufferedImage>();
- glyphCache = new HashMap<Cell, BufferedImage>();
-
- // Setup triple-buffering
- if (SwingScreen.tripleBuffer) {
- setIgnoreRepaint(true);
- createBufferStrategy(3);
- bufferStrategy = getBufferStrategy();
- }
- }
-
- /**
- * 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
- */
- private boolean getFontAdjustments() {
- BufferedImage image = null;
-
- // What SHOULD happen is that the topmost/leftmost white pixel is
- // at position (gr2x, gr2y). But it might also be off by a pixel
- // in either direction.
-
- Graphics2D gr2 = null;
- int gr2x = 3;
- int gr2y = 3;
- image = new BufferedImage(textWidth * 2, textHeight * 2,
- BufferedImage.TYPE_INT_ARGB);
-
- gr2 = image.createGraphics();
- gr2.setFont(getFont());
- gr2.setColor(java.awt.Color.BLACK);
- gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
- gr2.setColor(java.awt.Color.WHITE);
- char [] chars = new char[1];
- chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR;
- gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - 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(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();
-
- 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) {
- textAdjustX = (gr2x - x);
-
- // System.err.println("textAdjustX: " + textAdjustX);
- return true;
- }
- }
- }
-
- // Something weird happened, don't rely on this function.
- // System.err.println("getFontAdjustments: false");
- return false;
- }
-
-
- /**
- * Figure out my font dimensions.
- */
- private void getFontDimensions() {
- Graphics gr = getGraphics();
- FontMetrics fm = gr.getFontMetrics();
- 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;
-
- // This produces the same number, but works better for ugly
- // monospace.
- textHeight = 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;
- }
- }
- }
-
- /**
- * Resize to font dimensions.
- */
- public void resizeToScreen() {
- // Figure out the thickness of borders and use that to set the
- // final size.
- Insets insets = getInsets();
- left = insets.left;
- top = insets.top;
-
- setSize(textWidth * screen.width + insets.left + insets.right,
- textHeight * screen.height + insets.top + insets.bottom);
- }
-
- /**
- * Update redraws the whole screen.
- *
- * @param gr the Swing Graphics context
- */
- @Override
- public void update(final Graphics gr) {
- // The default update clears the area. Don't do that, instead
- // just paint it directly.
- paint(gr);
- }
-
- /**
- * Draw one glyph 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 drawGlyph(final Graphics gr, final Cell cell,
- final int xPixel, final int yPixel) {
-
- BufferedImage image = null;
- if (cell.isBlink() && !cursorBlinkVisible) {
- image = glyphCacheBlink.get(cell);
- } else {
- image = glyphCache.get(cell);
- }
- if (image != null) {
- gr.drawImage(image, xPixel, yPixel, this);
- return;
- }
-
- // Generate glyph and draw it.
- Graphics2D gr2 = null;
- int gr2x = xPixel;
- int gr2y = yPixel;
- if (tripleBuffer) {
- image = new BufferedImage(textWidth, textHeight,
- BufferedImage.TYPE_INT_ARGB);
- gr2 = image.createGraphics();
- gr2.setFont(getFont());
- gr2x = 0;
- gr2y = 0;
- } else {
- gr2 = (Graphics2D) gr;
- }
-
- Cell cellColor = new Cell();
- cellColor.setTo(cell);
-
- // Check for reverse
- if (cell.isReverse()) {
- cellColor.setForeColor(cell.getBackColor());
- cellColor.setBackColor(cell.getForeColor());
- }
-
- // Draw the background rectangle, then the foreground character.
- gr2.setColor(attrToBackgroundColor(cellColor));
- gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
-
- // Handle blink and underline
- if (!cell.isBlink()
- || (cell.isBlink() && cursorBlinkVisible)
- ) {
- gr2.setColor(attrToForegroundColor(cellColor));
- char [] chars = new char[1];
- chars[0] = cell.getChar();
- gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
- gr2y + textHeight - maxDescent + textAdjustY);
-
- if (cell.isUnderline()) {
- gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
- }
- }
-
- if (tripleBuffer) {
- gr2.dispose();
-
- // We need a new key that will not be mutated by
- // invertCell().
- Cell key = new Cell();
- key.setTo(cell);
- if (cell.isBlink() && !cursorBlinkVisible) {
- glyphCacheBlink.put(key, image);
- } else {
- glyphCache.put(key, image);
- }
-
- gr.drawImage(image, xPixel, yPixel, this);
- }
-
- }
-
- /**
- * Check if the cursor is visible, and if so draw it.
- *
- * @param gr the Swing Graphics context
- */
- private void drawCursor(final Graphics gr) {
-
- if (cursorVisible
- && (cursorY <= screen.height - 1)
- && (cursorX <= screen.width - 1)
- && cursorBlinkVisible
- ) {
- int xPixel = cursorX * textWidth + left;
- int yPixel = cursorY * textHeight + top;
- Cell lCell = screen.logical[cursorX][cursorY];
- gr.setColor(attrToForegroundColor(lCell));
- switch (cursorStyle) {
- default:
- // Fall through...
- case UNDERLINE:
- gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
- break;
- case BLOCK:
- gr.fillRect(xPixel, yPixel, textWidth, textHeight);
- break;
- case OUTLINE:
- gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
- break;
- }
- }
- }
-
- /**
- * Paint redraws the whole screen.
- *
- * @param gr the Swing Graphics context
- */
- @Override
- public void paint(final Graphics gr) {
- // Do nothing until the screen reference has been set.
- if (screen == null) {
- return;
- }
- if (screen.frame == null) {
- return;
- }
-
- // See if it is time to flip the blink time.
- long nowTime = (new Date()).getTime();
- if (nowTime > blinkMillis + lastBlinkTime) {
- lastBlinkTime = nowTime;
- cursorBlinkVisible = !cursorBlinkVisible;
- }
-
- int xCellMin = 0;
- int xCellMax = screen.width;
- int yCellMin = 0;
- int yCellMax = screen.height;
-
- Rectangle bounds = gr.getClipBounds();
- if (bounds != null) {
- // Only update what is in the bounds
- xCellMin = screen.textColumn(bounds.x);
- xCellMax = screen.textColumn(bounds.x + bounds.width);
- if (xCellMax > screen.width) {
- xCellMax = screen.width;
- }
- if (xCellMin >= xCellMax) {
- xCellMin = xCellMax - 2;
- }
- if (xCellMin < 0) {
- xCellMin = 0;
- }
- yCellMin = screen.textRow(bounds.y);
- yCellMax = screen.textRow(bounds.y + bounds.height);
- if (yCellMax > screen.height) {
- yCellMax = screen.height;
- }
- if (yCellMin >= yCellMax) {
- yCellMin = yCellMax - 2;
- }
- if (yCellMin < 0) {
- yCellMin = 0;
- }
- } else {
- // We need a total repaint
- reallyCleared = true;
- }
-
- // Prevent updates to the screen's data from the TApplication
- // threads.
- synchronized (screen) {
- /*
- System.err.printf("bounds %s X %d %d Y %d %d\n",
- bounds, xCellMin, xCellMax, yCellMin, yCellMax);
- */
-
- for (int y = yCellMin; y < yCellMax; y++) {
- for (int x = xCellMin; x < xCellMax; x++) {
-
- int xPixel = x * textWidth + left;
- int yPixel = y * textHeight + top;
-
- Cell lCell = screen.logical[x][y];
- Cell pCell = screen.physical[x][y];
-
- if (!lCell.equals(pCell)
- || lCell.isBlink()
- || reallyCleared) {
-
- drawGlyph(gr, lCell, xPixel, yPixel);
-
- // Physical is always updated
- physical[x][y].setTo(lCell);
- }
- }
- }
- drawCursor(gr);
-
- dirty = false;
- reallyCleared = false;
- } // synchronized (screen)
- }
-
- } // class SwingFrame
-
- /**
- * The raw Swing JFrame. Note package private access.
- */
- SwingFrame frame;
-
- /**
- * Restore terminal to normal state.
- */
- public void shutdown() {
- frame.dispose();
- }
-
- /**
- * Public constructor.
- *
- * @param windowWidth the number of text columns to start with
- * @param windowHeight the number of text rows to start with
- * @param fontSize the size in points. Good values to pick are: 16, 20,
- * 22, and 24.
- */
- public SwingScreen(final int windowWidth, final int windowHeight,
- final int fontSize) {
-
- try {
- SwingUtilities.invokeAndWait(new Runnable() {
- public void run() {
- SwingScreen.this.frame = new SwingFrame(SwingScreen.this,
- fontSize);
- SwingScreen.this.sessionInfo =
- new SwingSessionInfo(SwingScreen.this.frame,
- frame.textWidth, frame.textHeight,
- windowWidth, windowHeight);
-
- SwingScreen.this.setDimensions(sessionInfo.getWindowWidth(),
- sessionInfo.getWindowHeight());
-
- SwingScreen.this.frame.resizeToScreen();
- SwingScreen.this.frame.setVisible(true);
- }
- });
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /**
- * The sessionInfo.
- */
- private SwingSessionInfo sessionInfo;
-
- /**
- * Create the SwingSessionInfo. Note package private access.
- *
- * @return the sessionInfo
- */
- SwingSessionInfo getSessionInfo() {
- return sessionInfo;
- }
-
- /**
- * Push the logical screen to the physical device.
- */
- @Override
- public void flushPhysical() {
-
- /*
- System.err.printf("flushPhysical(): reallyCleared %s dirty %s\n",
- reallyCleared, dirty);
- */
-
- // If reallyCleared is set, we have to draw everything.
- if ((frame.bufferStrategy != null) && (reallyCleared == true)) {
- // Triple-buffering: we have to redraw everything on this thread.
- Graphics gr = frame.bufferStrategy.getDrawGraphics();
- frame.paint(gr);
- gr.dispose();
- frame.bufferStrategy.show();
- // sync() doesn't seem to help the tearing for me.
- // Toolkit.getDefaultToolkit().sync();
- return;
- } else if ((frame.bufferStrategy == null) && (reallyCleared == true)) {
- // Repaint everything on the Swing thread.
- frame.repaint();
- return;
- }
-
- // Do nothing if nothing happened.
- if (!dirty) {
- return;
- }
-
- if (frame.bufferStrategy != null) {
- // See if it is time to flip the blink time.
- long nowTime = (new Date()).getTime();
- if (nowTime > frame.blinkMillis + frame.lastBlinkTime) {
- frame.lastBlinkTime = nowTime;
- frame.cursorBlinkVisible = !frame.cursorBlinkVisible;
- }
-
- Graphics gr = frame.bufferStrategy.getDrawGraphics();
-
- synchronized (this) {
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- Cell lCell = logical[x][y];
- Cell pCell = physical[x][y];
-
- int xPixel = x * frame.textWidth + frame.left;
- int yPixel = y * frame.textHeight + frame.top;
-
- if (!lCell.equals(pCell)
- || ((x == cursorX)
- && (y == cursorY)
- && cursorVisible)
- || (lCell.isBlink())
- ) {
- frame.drawGlyph(gr, lCell, xPixel, yPixel);
- physical[x][y].setTo(lCell);
- }
- }
- }
- frame.drawCursor(gr);
- } // synchronized (this)
-
- gr.dispose();
- frame.bufferStrategy.show();
- // sync() doesn't seem to help the tearing for me.
- // Toolkit.getDefaultToolkit().sync();
- return;
- }
-
- // Swing thread version: request a repaint, but limit it to the area
- // that has changed.
-
- // Find the minimum-size damaged region.
- int xMin = frame.getWidth();
- int xMax = 0;
- int yMin = frame.getHeight();
- int yMax = 0;
-
- synchronized (this) {
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- Cell lCell = logical[x][y];
- Cell pCell = physical[x][y];
-
- int xPixel = x * frame.textWidth + frame.left;
- int yPixel = y * frame.textHeight + frame.top;
-
- if (!lCell.equals(pCell)
- || ((x == cursorX)
- && (y == cursorY)
- && cursorVisible)
- || lCell.isBlink()
- ) {
- if (xPixel < xMin) {
- xMin = xPixel;
- }
- if (xPixel + frame.textWidth > xMax) {
- xMax = xPixel + frame.textWidth;
- }
- if (yPixel < yMin) {
- yMin = yPixel;
- }
- if (yPixel + frame.textHeight > yMax) {
- yMax = yPixel + frame.textHeight;
- }
- }
- }
- }
- }
- if (xMin + frame.textWidth >= xMax) {
- xMax += frame.textWidth;
- }
- if (yMin + frame.textHeight >= yMax) {
- yMax += frame.textHeight;
- }
-
- // Repaint the desired area
- /*
- System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
- yMin, yMax);
- */
- if (frame.bufferStrategy != null) {
- // This path should never be taken, but is left here for
- // completeness.
- Graphics gr = frame.bufferStrategy.getDrawGraphics();
- Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
- yMax - yMin);
- gr.setClip(bounds);
- frame.paint(gr);
- gr.dispose();
- frame.bufferStrategy.show();
- // sync() doesn't seem to help the tearing for me.
- // Toolkit.getDefaultToolkit().sync();
- } else {
- // Repaint on the Swing thread.
- frame.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
- }
- }
-
- /**
- * Put the cursor at (x,y).
- *
- * @param visible if true, the cursor should be visible
- * @param x column coordinate to put the cursor on
- * @param y row coordinate to put the cursor on
- */
- @Override
- public void putCursor(final boolean visible, final int x, final int y) {
-
- if ((visible == cursorVisible) && ((x == cursorX) && (y == cursorY))) {
- // See if it is time to flip the blink time.
- long nowTime = (new Date()).getTime();
- if (nowTime < frame.blinkMillis + frame.lastBlinkTime) {
- // Nothing has changed, so don't do anything.
- return;
- }
- }
-
- if (cursorVisible
- && (cursorY <= height - 1)
- && (cursorX <= width - 1)
- ) {
- // Make the current cursor position dirty
- if (physical[cursorX][cursorY].getChar() == 'Q') {
- physical[cursorX][cursorY].setChar('X');
- } else {
- physical[cursorX][cursorY].setChar('Q');
- }
- }
-
- super.putCursor(visible, x, y);
- }
-
- /**
- * Convert pixel column position to text cell column position.
- *
- * @param x pixel column position
- * @return text cell column position
- */
- public int textColumn(final int x) {
- return ((x - frame.left) / frame.textWidth);
- }
-
- /**
- * Convert pixel row position to text cell row position.
- *
- * @param y pixel row position
- * @return text cell row position
- */
- public int textRow(final int y) {
- return ((y - frame.top) / frame.textHeight);
- }
-
- /**
- * Set the window title.
- *
- * @param title the new title
- */
- public void setTitle(final String title) {
- frame.setTitle(title);
- }
-
-}
+++ /dev/null
-/*
- * Jexer - Java Text User Interface
- *
- * The MIT License (MIT)
- *
- * Copyright (C) 2017 Kevin 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 Kevin Lamonte [kevin.lamonte@gmail.com]
- * @version 1
- */
-package jexer.io;
-
-import java.awt.event.ComponentEvent;
-import java.awt.event.ComponentListener;
-import java.awt.event.KeyEvent;
-import java.awt.event.KeyListener;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.MouseMotionListener;
-import java.awt.event.MouseWheelEvent;
-import java.awt.event.MouseWheelListener;
-import java.awt.event.WindowEvent;
-import java.awt.event.WindowListener;
-import java.util.List;
-import java.util.LinkedList;
-
-import jexer.TKeypress;
-import jexer.event.TCommandEvent;
-import jexer.event.TInputEvent;
-import jexer.event.TKeypressEvent;
-import jexer.event.TMouseEvent;
-import jexer.event.TResizeEvent;
-import jexer.session.SessionInfo;
-import jexer.session.SwingSessionInfo;
-import static jexer.TCommand.*;
-import static jexer.TKeypress.*;
-
-/**
- * This class reads keystrokes and mouse events from an Swing JFrame.
- */
-public final class SwingTerminal implements ComponentListener, KeyListener,
- MouseListener, MouseMotionListener,
- MouseWheelListener, WindowListener {
-
- /**
- * The backend Screen.
- */
- private SwingScreen screen;
-
- /**
- * The session information.
- */
- private SwingSessionInfo sessionInfo;
-
- /**
- * Getter for sessionInfo.
- *
- * @return the SessionInfo
- */
- public SessionInfo getSessionInfo() {
- return sessionInfo;
- }
-
- /**
- * The listening object that run() wakes up on new input.
- */
- private Object listener;
-
- /**
- * The event queue, filled up by a thread reading on input.
- */
- private List<TInputEvent> eventQueue;
-
- /**
- * The last reported mouse X position.
- */
- private int oldMouseX = -1;
-
- /**
- * The last reported mouse Y position.
- */
- private int oldMouseY = -1;
-
- /**
- * true if mouse1 was down. Used to report mouse1 on the release event.
- */
- private boolean mouse1 = false;
-
- /**
- * true if mouse2 was down. Used to report mouse2 on the release event.
- */
- private boolean mouse2 = false;
-
- /**
- * true if mouse3 was down. Used to report mouse3 on the release event.
- */
- private boolean mouse3 = false;
-
- /**
- * Check if there are events in the queue.
- *
- * @return if true, getEvents() has something to return to the backend
- */
- public boolean hasEvents() {
- synchronized (eventQueue) {
- return (eventQueue.size() > 0);
- }
- }
-
- /**
- * Constructor sets up state for getEvent().
- *
- * @param listener the object this backend needs to wake up when new
- * input comes in
- * @param screen the top-level Swing frame
- */
- public SwingTerminal(final Object listener, final SwingScreen screen) {
- this.listener = listener;
- this.screen = screen;
- mouse1 = false;
- mouse2 = false;
- mouse3 = false;
- sessionInfo = screen.getSessionInfo();
- eventQueue = new LinkedList<TInputEvent>();
-
- screen.frame.addKeyListener(this);
- screen.frame.addWindowListener(this);
- screen.frame.addComponentListener(this);
- screen.frame.addMouseListener(this);
- screen.frame.addMouseMotionListener(this);
- screen.frame.addMouseWheelListener(this);
- }
-
- /**
- * Return any events in the IO queue.
- *
- * @param queue list to append new events to
- */
- public void getEvents(final List<TInputEvent> queue) {
- synchronized (eventQueue) {
- if (eventQueue.size() > 0) {
- synchronized (queue) {
- queue.addAll(eventQueue);
- }
- eventQueue.clear();
- }
- }
- }
-
- /**
- * Pass Swing keystrokes into the event queue.
- *
- * @param key keystroke received
- */
- public void keyReleased(final KeyEvent key) {
- // Ignore release events
- }
-
- /**
- * Pass Swing keystrokes into the event queue.
- *
- * @param key keystroke received
- */
- public void keyTyped(final KeyEvent key) {
- // Ignore typed events
- }
-
- /**
- * Pass Swing keystrokes into the event queue.
- *
- * @param key keystroke received
- */
- public void keyPressed(final KeyEvent key) {
- boolean alt = false;
- boolean shift = false;
- boolean ctrl = false;
- char ch = ' ';
- boolean isKey = false;
- if (key.isActionKey()) {
- isKey = true;
- } else {
- ch = key.getKeyChar();
- }
- alt = key.isAltDown();
- ctrl = key.isControlDown();
- shift = key.isShiftDown();
-
- /*
- System.err.printf("Swing Key: %s\n", key);
- System.err.printf(" isKey: %s\n", isKey);
- System.err.printf(" alt: %s\n", alt);
- System.err.printf(" ctrl: %s\n", ctrl);
- System.err.printf(" shift: %s\n", shift);
- System.err.printf(" ch: %s\n", ch);
- */
-
- // Special case: not return the bare modifier presses
- switch (key.getKeyCode()) {
- case KeyEvent.VK_ALT:
- return;
- case KeyEvent.VK_ALT_GRAPH:
- return;
- case KeyEvent.VK_CONTROL:
- return;
- case KeyEvent.VK_SHIFT:
- return;
- case KeyEvent.VK_META:
- return;
- default:
- break;
- }
-
- TKeypress keypress = null;
- if (isKey) {
- switch (key.getKeyCode()) {
- case KeyEvent.VK_F1:
- keypress = new TKeypress(true, TKeypress.F1, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F2:
- keypress = new TKeypress(true, TKeypress.F2, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F3:
- keypress = new TKeypress(true, TKeypress.F3, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F4:
- keypress = new TKeypress(true, TKeypress.F4, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F5:
- keypress = new TKeypress(true, TKeypress.F5, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F6:
- keypress = new TKeypress(true, TKeypress.F6, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F7:
- keypress = new TKeypress(true, TKeypress.F7, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F8:
- keypress = new TKeypress(true, TKeypress.F8, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F9:
- keypress = new TKeypress(true, TKeypress.F9, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F10:
- keypress = new TKeypress(true, TKeypress.F10, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F11:
- keypress = new TKeypress(true, TKeypress.F11, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_F12:
- keypress = new TKeypress(true, TKeypress.F12, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_HOME:
- keypress = new TKeypress(true, TKeypress.HOME, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_END:
- keypress = new TKeypress(true, TKeypress.END, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_PAGE_UP:
- keypress = new TKeypress(true, TKeypress.PGUP, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_PAGE_DOWN:
- keypress = new TKeypress(true, TKeypress.PGDN, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_INSERT:
- keypress = new TKeypress(true, TKeypress.INS, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_DELETE:
- keypress = new TKeypress(true, TKeypress.DEL, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_RIGHT:
- keypress = new TKeypress(true, TKeypress.RIGHT, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_LEFT:
- keypress = new TKeypress(true, TKeypress.LEFT, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_UP:
- keypress = new TKeypress(true, TKeypress.UP, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_DOWN:
- keypress = new TKeypress(true, TKeypress.DOWN, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_TAB:
- // Special case: distinguish TAB vs BTAB
- if (shift) {
- keypress = kbShiftTab;
- } else {
- keypress = kbTab;
- }
- break;
- case KeyEvent.VK_ENTER:
- keypress = new TKeypress(true, TKeypress.ENTER, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_ESCAPE:
- keypress = new TKeypress(true, TKeypress.ESC, ' ',
- alt, ctrl, shift);
- break;
- case KeyEvent.VK_BACK_SPACE:
- // Special case: return it as kbBackspace (Ctrl-H)
- keypress = new TKeypress(false, 0, 'H', false, true, false);
- break;
- default:
- // Unsupported, ignore
- return;
- }
- }
-
- if (keypress == null) {
- switch (ch) {
- case 0x08:
- keypress = kbBackspace;
- break;
- case 0x0A:
- keypress = kbEnter;
- break;
- case 0x1B:
- keypress = kbEsc;
- break;
- case 0x0D:
- keypress = kbEnter;
- break;
- case 0x09:
- if (shift) {
- keypress = kbShiftTab;
- } else {
- keypress = kbTab;
- }
- break;
- case 0x7F:
- keypress = kbDel;
- break;
- default:
- if (!alt && ctrl && !shift) {
- ch = KeyEvent.getKeyText(key.getKeyCode()).charAt(0);
- }
- // Not a special key, put it together
- keypress = new TKeypress(false, 0, ch, alt, ctrl, shift);
- }
- }
-
- // Save it and we are done.
- synchronized (eventQueue) {
- eventQueue.add(new TKeypressEvent(keypress));
- }
- synchronized (listener) {
- listener.notifyAll();
- }
- }
-
- /**
- * Pass window events into the event queue.
- *
- * @param event window event received
- */
- public void windowActivated(final WindowEvent event) {
- // Force a total repaint
- synchronized (screen) {
- screen.clearPhysical();
- }
- }
-
- /**
- * Pass window events into the event queue.
- *
- * @param event window event received
- */
- public void windowClosed(final WindowEvent event) {
- // Ignore
- }
-
- /**
- * Pass window events into the event queue.
- *
- * @param event window event received
- */
- public void windowClosing(final WindowEvent event) {
- // Drop a cmAbort and walk away
- synchronized (eventQueue) {
- eventQueue.add(new TCommandEvent(cmAbort));
- }
- synchronized (listener) {
- listener.notifyAll();
- }
- }
-
- /**
- * Pass window events into the event queue.
- *
- * @param event window event received
- */
- public void windowDeactivated(final WindowEvent event) {
- // Ignore
- }
-
- /**
- * Pass window events into the event queue.
- *
- * @param event window event received
- */
- public void windowDeiconified(final WindowEvent event) {
- // Ignore
- }
-
- /**
- * Pass window events into the event queue.
- *
- * @param event window event received
- */
- public void windowIconified(final WindowEvent event) {
- // Ignore
- }
-
- /**
- * Pass window events into the event queue.
- *
- * @param event window event received
- */
- public void windowOpened(final WindowEvent event) {
- // Ignore
- }
-
- /**
- * Pass component events into the event queue.
- *
- * @param event component event received
- */
- public void componentHidden(final ComponentEvent event) {
- // Ignore
- }
-
- /**
- * Pass component events into the event queue.
- *
- * @param event component event received
- */
- public void componentShown(final ComponentEvent event) {
- // Ignore
- }
-
- /**
- * Pass component events into the event queue.
- *
- * @param event component event received
- */
- public void componentMoved(final ComponentEvent event) {
- // Ignore
- }
-
- /**
- * Pass component events into the event queue.
- *
- * @param event component event received
- */
- public void componentResized(final ComponentEvent event) {
- // Drop a new TResizeEvent into the queue
- sessionInfo.queryWindowSize();
- synchronized (eventQueue) {
- TResizeEvent windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
- sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
- eventQueue.add(windowResize);
- }
- synchronized (listener) {
- listener.notifyAll();
- }
- }
-
- /**
- * Pass mouse events into the event queue.
- *
- * @param mouse mouse event received
- */
- public void mouseDragged(final MouseEvent mouse) {
- int modifiers = mouse.getModifiersEx();
- boolean eventMouse1 = false;
- boolean eventMouse2 = false;
- boolean eventMouse3 = false;
- if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
- eventMouse1 = true;
- }
- if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
- eventMouse2 = true;
- }
- if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
- eventMouse3 = true;
- }
- mouse1 = eventMouse1;
- mouse2 = eventMouse2;
- mouse3 = eventMouse3;
- int x = screen.textColumn(mouse.getX());
- int y = screen.textRow(mouse.getY());
-
- TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
- x, y, x, y, mouse1, mouse2, mouse3, false, false);
-
- synchronized (eventQueue) {
- eventQueue.add(mouseEvent);
- }
- synchronized (listener) {
- listener.notifyAll();
- }
- }
-
- /**
- * Pass mouse events into the event queue.
- *
- * @param mouse mouse event received
- */
- public void mouseMoved(final MouseEvent mouse) {
- int x = screen.textColumn(mouse.getX());
- int y = screen.textRow(mouse.getY());
- if ((x == oldMouseX) && (y == oldMouseY)) {
- // Bail out, we've moved some pixels but not a whole text cell.
- return;
- }
- oldMouseX = x;
- oldMouseY = y;
-
- TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
- x, y, x, y, mouse1, mouse2, mouse3, false, false);
-
- synchronized (eventQueue) {
- eventQueue.add(mouseEvent);
- }
- synchronized (listener) {
- listener.notifyAll();
- }
- }
-
- /**
- * Pass mouse events into the event queue.
- *
- * @param mouse mouse event received
- */
- public void mouseClicked(final MouseEvent mouse) {
- // Ignore
- }
-
- /**
- * Pass mouse events into the event queue.
- *
- * @param mouse mouse event received
- */
- public void mouseEntered(final MouseEvent mouse) {
- // Ignore
- }
-
- /**
- * Pass mouse events into the event queue.
- *
- * @param mouse mouse event received
- */
- public void mouseExited(final MouseEvent mouse) {
- // Ignore
- }
-
- /**
- * Pass mouse events into the event queue.
- *
- * @param mouse mouse event received
- */
- public void mousePressed(final MouseEvent mouse) {
- int modifiers = mouse.getModifiersEx();
- boolean eventMouse1 = false;
- boolean eventMouse2 = false;
- boolean eventMouse3 = false;
- if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
- eventMouse1 = true;
- }
- if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
- eventMouse2 = true;
- }
- if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
- eventMouse3 = true;
- }
- mouse1 = eventMouse1;
- mouse2 = eventMouse2;
- mouse3 = eventMouse3;
- int x = screen.textColumn(mouse.getX());
- int y = screen.textRow(mouse.getY());
-
- TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
- x, y, x, y, mouse1, mouse2, mouse3, false, false);
-
- synchronized (eventQueue) {
- eventQueue.add(mouseEvent);
- }
- synchronized (listener) {
- listener.notifyAll();
- }
- }
-
- /**
- * Pass mouse events into the event queue.
- *
- * @param mouse mouse event received
- */
- public void mouseReleased(final MouseEvent mouse) {
- int modifiers = mouse.getModifiersEx();
- boolean eventMouse1 = false;
- boolean eventMouse2 = false;
- boolean eventMouse3 = false;
- if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
- eventMouse1 = true;
- }
- if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
- eventMouse2 = true;
- }
- if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
- eventMouse3 = true;
- }
- if (mouse1) {
- mouse1 = false;
- eventMouse1 = true;
- }
- if (mouse2) {
- mouse2 = false;
- eventMouse2 = true;
- }
- if (mouse3) {
- mouse3 = false;
- eventMouse3 = true;
- }
- int x = screen.textColumn(mouse.getX());
- int y = screen.textRow(mouse.getY());
-
- TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP,
- x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false);
-
- synchronized (eventQueue) {
- eventQueue.add(mouseEvent);
- }
- synchronized (listener) {
- listener.notifyAll();
- }
- }
-
- /**
- * Pass mouse events into the event queue.
- *
- * @param mouse mouse event received
- */
- public void mouseWheelMoved(final MouseWheelEvent mouse) {
- int modifiers = mouse.getModifiersEx();
- boolean eventMouse1 = false;
- boolean eventMouse2 = false;
- boolean eventMouse3 = false;
- boolean mouseWheelUp = false;
- boolean mouseWheelDown = false;
- if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
- eventMouse1 = true;
- }
- if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
- eventMouse2 = true;
- }
- if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
- eventMouse3 = true;
- }
- mouse1 = eventMouse1;
- mouse2 = eventMouse2;
- mouse3 = eventMouse3;
- int x = screen.textColumn(mouse.getX());
- int y = screen.textRow(mouse.getY());
- if (mouse.getWheelRotation() > 0) {
- mouseWheelDown = true;
- }
- if (mouse.getWheelRotation() < 0) {
- mouseWheelUp = true;
- }
-
- TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
- x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown);
-
- synchronized (eventQueue) {
- eventQueue.add(mouseEvent);
- }
- synchronized (listener) {
- listener.notifyAll();
- }
- }
-
-}
}
/**
- * Public constructor, using the default 10 bits per byte.
+ * Public constructor.
*
* @param stream the wrapped InputStream
* @param timeoutMillis the timeout value in millis. If it takes longer
*/
/**
- * User-facing I/O, including screen, keyboard, and mouse handling classes.
+ * java.io subclasses.
*/
package jexer.io;
import java.util.Map;
import java.util.TreeMap;
-import jexer.session.SessionInfo;
+import jexer.backend.SessionInfo;
import static jexer.net.TelnetSocket.*;
/**