Jexer - Java Text User Interface library
========================================
-WARNING: THIS IS ALPHA CODE!
+WARNING: THIS IS ALPHA CODE! PLEASE CONSIDER FILING BUGS AS YOU
+ENCOUNTER THEM.
This library is intended to implement a text-based windowing system
loosely reminiscient of Borland's [Turbo
(tested on Linux + xterm). I/O is handled through terminal escape
sequences generated by the library itself: ncurses is not required
or linked to. xterm mouse tracking using UTF8 coordinates is
- supported. This is the default backend on non-Windows platforms.
+ supported. For the demo application, this is the default backend on
+ non-Windows platforms.
-* Java AWT UI. This backend can be selected by setting
- jexer.AWT=true. This is the default backend on Windows platforms.
- AWT is experimental, please consider filing bugs when you encounter
- them. The default window size for AWT is 132x40, which is set in
- jexer.session.AWTSession.
+* Java Swing UI. This backend can be selected by setting
+ jexer.Swing=true. The default window size for Swing is 132x40,
+ which is set in jexer.session.SwingSession. For the demo
+ application, this is the default backend on Windows platforms.
-A demo application showing the existing UI controls is available via
-'java -jar jexer.jar' or 'java -Djexer.AWT=true -jar jexer.jar' .
+The demo application showing the existing UI controls is available via
+'java -jar jexer.jar' or 'java -Djexer.Swing=true -jar jexer.jar' .
+
+Additional backends can be created by subclassing
+jexer.backend.Backend and passing it into the TApplication
+constructor.
-----
Usage patterns are still being worked on, but in general the goal will
-be to build applications somewhat as follows:
+be to build applications as follows:
```Java
import jexer.*;
public class MyApplication extends TApplication {
public MyApplication() {
- super();
+ super(BackendType.SWING); // Could also use BackendType.XTERM
// Create standard menus for File and Window
addFileMenu();
public static void main(String [] args) {
MyApplication app = new MyApplication();
- app.run();
+ (new Thread(app)).start();
}
}
```
- See jexer.tterminal.ECMA48 for more specifics of terminal
emulation limitations.
+ - TTerminalWindow uses cmd.exe on Windows. Output will not be seen
+ until enter is pressed, due to cmd.exe's use of line-oriented
+ input (see the ENABLE_LINE_INPUT flag for GetConsoleMode() and
+ SetConsoleMode()).
Many tasks remain before calling this version 1.0:
-0.0.2:
+0.0.2: STABILIZE EXISTING
-- Making TMenu keyboard accelerators active/inactive
-- AWT:
+- TTerminalWindow
+ - Expose shell commands as properties
+- Swing:
- Blinking cursor
- Block cursor
- ECMA48Backend running on socket
-- TTreeView
-- TDirectoryList
- TFileOpen
- Decide on naming convention: getText, getValue, getLabel: one or all
of them?
- getCh() --> getChar()
- getAlt/getCtrl/getShift --> isAltDown / isCtrlDown / isShiftDown
- Other boolean getters --> isSomething
+- Document any properties used
+ - Expose use of 'stty'
+
+0.0.3: FINISH PORTING
-0.0.3:
+- TTreeView
+ - Also add keyboard navigation
+- TDirectoryList
+ - Also add keyboard navigation
+
+0.0.4: NEW STUFF
+- Making TMenu keyboard accelerators active/inactive
- TStatusBar
- TEditor
+- TWindow
+ - "Smart placement" for new windows
-0.0.4:
+0.0.5: BUG HUNT
-- Bugs
- - TSubMenu keyboard mnemonic not working
- - TDirectoryList cannot be navigated only with keyboard
- - TTreeView cannot be navigated only with keyboard
- - RangeViolation after dragging scrollbar up/down
+- TSubMenu keyboard mnemonic not working
-0.1.0:
+0.1.0: BETA RELEASE
-- TWindow
- - "Smart placement" for new windows
+- TSpinner
+- TComboBox
+- TListBox
+- TCalendar
+- TColorPicker
Wishlist features (2.0):
- Handle resize events (pass to child process)
- Screen
- Allow complex characters in putCharXY() and detect them in putStrXY().
-- TComboBox
-- TListBox
-- TSpinner
-- TCalendar widget
-- TColorPicker widget
- Drag and drop
- TEditor
- TField
<property name="build.dir" value="build"/>
<property name="classes.dir" value="${build.dir}/classes"/>
<property name="jar.dir" value="${build.dir}/jar"/>
+ <property name="apidocs.dir" value="docs/api"/>
<target name="clean">
<delete dir="${build.dir}"/>
+ <delete dir="${apidocs.dir}"/>
</target>
<target name="compile">
<target name="run" depends="jar">
<java jar="${jar.dir}/${ant.project.name}.jar" fork="true">
- <arg value="-Djexer.AWT=true"/>
+ <arg value="-Djexer.Swing=true"/>
</java>
</target>
<target name="docs" depends="jar">
<javadoc
- destdir="docs/api"
+ destdir="${apidocs.dir}"
author="true"
version="true"
use="true"
import jexer.event.TMouseEvent;
import jexer.event.TResizeEvent;
import jexer.backend.Backend;
-import jexer.backend.AWTBackend;
+import jexer.backend.SwingBackend;
import jexer.backend.ECMA48Backend;
import jexer.io.Screen;
import jexer.menu.TMenu;
/**
* TApplication sets up a full Text User Interface application.
*/
-public class TApplication {
+public class TApplication implements Runnable {
/**
* If true, emit thread stuff to System.err.
*/
private static final boolean debugEvents = false;
+ /**
+ * Two backend types are available.
+ */
+ public static enum BackendType {
+ /**
+ * A Swing JFrame.
+ */
+ SWING,
+
+ /**
+ * An ECMA48 / ANSI X3.64 / XTERM style terminal.
+ */
+ ECMA48,
+
+ /**
+ * Synonym for ECMA48
+ */
+ XTERM
+ }
+
/**
* WidgetEventHandler is the main event consumer loop. There are at most
* two such threads in existence: the primary for normal case and a
/**
* Public constructor.
*
+ * @param backendType BackendType.XTERM, BackendType.ECMA48 or
+ * BackendType.SWING
+ * @throws UnsupportedEncodingException if an exception is thrown when
+ * creating the InputStreamReader
+ */
+ public TApplication(final BackendType backendType)
+ throws UnsupportedEncodingException {
+
+ switch (backendType) {
+ case SWING:
+ backend = new SwingBackend(this);
+ break;
+ case XTERM:
+ // Fall through...
+ case ECMA48:
+ backend = new ECMA48Backend(this, null, null);
+ }
+ TApplicationImpl();
+ }
+
+ /**
+ * Public constructor. The backend type will be BackendType.ECMA48.
+ *
* @param input an InputStream connected to the remote user, or null for
* System.in. If System.in is used, then on non-Windows systems it will
* be put in raw mode; shutdown() will (blindly!) put System.in in cooked
public TApplication(final InputStream input,
final OutputStream output) throws UnsupportedEncodingException {
- // AWT is the default backend on Windows unless explicitly overridden
- // by jexer.AWT.
- boolean useAWT = false;
- if (System.getProperty("os.name").startsWith("Windows")) {
- useAWT = true;
- }
- if (System.getProperty("jexer.AWT") != null) {
- if (System.getProperty("jexer.AWT", "false").equals("true")) {
- useAWT = true;
- } else {
- useAWT = false;
- }
- }
+ backend = new ECMA48Backend(this, input, output);
+ TApplicationImpl();
+ }
+ /**
+ * Public constructor. This hook enables use with new non-Jexer
+ * backends.
+ *
+ * @param backend a Backend that is already ready to go.
+ */
+ public TApplication(final Backend backend) {
+ this.backend = backend;
+ TApplicationImpl();
+ }
- if (useAWT) {
- backend = new AWTBackend(this);
- } else {
- backend = new ECMA48Backend(this, input, output);
- }
+ /**
+ * Finish construction once the backend is set.
+ */
+ private void TApplicationImpl() {
theme = new ColorTheme();
desktopBottom = getScreen().getHeight() - 1;
fillEventQueue = new ArrayList<TInputEvent>();
/**
* Run this application until it exits.
*/
- public final void run() {
+ public void run() {
while (!quit) {
// Timeout is in milliseconds, so default timeout after 1 second
// of inactivity.
import java.util.List;
import jexer.event.TInputEvent;
-import jexer.io.AWTScreen;
-import jexer.io.AWTTerminal;
+import jexer.io.SwingScreen;
+import jexer.io.SwingTerminal;
/**
- * This class uses standard AWT calls to handle screen, keyboard, and mouse
+ * This class uses standard Swing calls to handle screen, keyboard, and mouse
* I/O.
*/
-public final class AWTBackend extends Backend {
+public final class SwingBackend extends Backend {
/**
* Input events are processed by this Terminal.
*/
- private AWTTerminal terminal;
+ private SwingTerminal terminal;
/**
* Public constructor.
* @param listener the object this backend needs to wake up when new
* input comes in
*/
- public AWTBackend(final Object listener) {
+ public SwingBackend(final Object listener) {
// Create a screen
- AWTScreen screen = new AWTScreen();
+ SwingScreen screen = new SwingScreen();
this.screen = screen;
- // Create the AWT event listeners
- terminal = new AWTTerminal(listener, screen);
+ // Create the Swing event listeners
+ terminal = new SwingTerminal(listener, screen);
// Hang onto the session info
this.sessionInfo = terminal.getSessionInfo();
/**
* Public constructor.
*
+ * @param backendType one of the TApplication.BackendType values
* @throws Exception if TApplication can't instantiate the Backend.
*/
- public DemoApplication() throws Exception {
- super(null, null);
+ public DemoApplication(BackendType backendType) throws Exception {
+ super(backendType);
new DemoMainWindow(this);
// Add the menus
*/
public static void main(final String [] args) {
try {
- DemoApplication app = new DemoApplication();
- app.run();
+ // Swing is the default backend on Windows unless explicitly
+ // overridden by jexer.Swing.
+ TApplication.BackendType backendType = TApplication.BackendType.XTERM;
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ backendType = TApplication.BackendType.SWING;
+ }
+ if (System.getProperty("jexer.Swing") != null) {
+ if (System.getProperty("jexer.Swing", "false").equals("true")) {
+ backendType = TApplication.BackendType.SWING;
+ } else {
+ backendType = TApplication.BackendType.XTERM;
+ }
+ }
+ DemoApplication app = new DemoApplication(backendType);
+ (new Thread(app)).start();
} catch (Exception e) {
e.printStackTrace();
}
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
-import jexer.session.AWTSessionInfo;
+import jexer.session.SwingSessionInfo;
import java.awt.Color;
import java.awt.Cursor;
import javax.swing.SwingUtilities;
/**
- * This Screen implementation draws to a Java AWT Frame.
+ * This Screen implementation draws to a Java Swing JFrame.
*/
-public final class AWTScreen extends Screen {
+public final class SwingScreen extends Screen {
private static Color MYBLACK;
private static Color MYRED;
private static boolean dosColors = false;
/**
- * Setup AWT colors to match DOS color palette.
+ * Setup Swing colors to match DOS color palette.
*/
private static void setDOSColors() {
if (dosColors) {
}
/**
- * AWTFrame is our top-level hook into the AWT system.
+ * SwingFrame is our top-level hook into the Swing system.
*/
- class AWTFrame extends JFrame {
+ class SwingFrame extends JFrame {
/**
* Serializable version.
/**
* The TUI Screen data.
*/
- AWTScreen screen;
+ SwingScreen screen;
/**
* Width of a character cell.
private int left = 30;
/**
- * Convert a CellAttributes foreground color to an AWT Color.
+ * Convert a CellAttributes foreground color to an Swing Color.
*
* @param attr the text attributes
- * @return the AWT Color
+ * @return the Swing Color
*/
private Color attrToForegroundColor(final CellAttributes attr) {
/*
}
/**
- * Convert a CellAttributes background color to an AWT Color.
+ * Convert a CellAttributes background color to an Swing Color.
*
* @param attr the text attributes
- * @return the AWT Color
+ * @return the Swing Color
*/
private Color attrToBackgroundColor(final CellAttributes attr) {
/*
*
* @param screen the Screen that Backend talks to
*/
- public AWTFrame(final AWTScreen screen) {
+ public SwingFrame(final SwingScreen screen) {
this.screen = screen;
setDOSColors();
/**
* Update redraws the whole screen.
*
- * @param gr the AWT Graphics context
+ * @param gr the Swing Graphics context
*/
@Override
public void update(final Graphics gr) {
/**
* Paint redraws the whole screen.
*
- * @param gr the AWT Graphics context
+ * @param gr the Swing Graphics context
*/
@Override
public void paint(final Graphics gr) {
} // synchronized (screen)
}
- } // class AWTFrame
+ } // class SwingFrame
/**
- * The raw AWT Frame. Note package private access.
+ * The raw Swing JFrame. Note package private access.
*/
- AWTFrame frame;
+ SwingFrame frame;
/**
* Public constructor.
*/
- public AWTScreen() {
+ public SwingScreen() {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
- AWTScreen.this.frame = new AWTFrame(AWTScreen.this);
- AWTScreen.this.sessionInfo =
- new AWTSessionInfo(AWTScreen.this.frame,
+ SwingScreen.this.frame = new SwingFrame(SwingScreen.this);
+ SwingScreen.this.sessionInfo =
+ new SwingSessionInfo(SwingScreen.this.frame,
frame.textWidth,
frame.textHeight);
- AWTScreen.this.setDimensions(sessionInfo.getWindowWidth(),
+ SwingScreen.this.setDimensions(sessionInfo.getWindowWidth(),
sessionInfo.getWindowHeight());
- AWTScreen.this.frame.resizeToScreen();
- AWTScreen.this.frame.setVisible(true);
+ SwingScreen.this.frame.resizeToScreen();
+ SwingScreen.this.frame.setVisible(true);
}
} );
} catch (Exception e) {
/**
* The sessionInfo.
*/
- private AWTSessionInfo sessionInfo;
+ private SwingSessionInfo sessionInfo;
/**
- * Create the AWTSessionInfo. Note package private access.
+ * Create the SwingSessionInfo. Note package private access.
*
* @return the sessionInfo
*/
- AWTSessionInfo getSessionInfo() {
+ SwingSessionInfo getSessionInfo() {
return sessionInfo;
}
import jexer.event.TMouseEvent;
import jexer.event.TResizeEvent;
import jexer.session.SessionInfo;
-import jexer.session.AWTSessionInfo;
+import jexer.session.SwingSessionInfo;
import static jexer.TCommand.*;
import static jexer.TKeypress.*;
/**
- * This class reads keystrokes and mouse events from an AWT Frame.
+ * This class reads keystrokes and mouse events from an Swing JFrame.
*/
-public final class AWTTerminal implements ComponentListener, KeyListener,
+public final class SwingTerminal implements ComponentListener, KeyListener,
MouseListener, MouseMotionListener,
MouseWheelListener, WindowListener {
/**
* The backend Screen.
*/
- private AWTScreen screen;
+ private SwingScreen screen;
/**
* The session information.
*/
- private AWTSessionInfo sessionInfo;
+ private SwingSessionInfo sessionInfo;
/**
* Getter for sessionInfo.
*
* @param listener the object this backend needs to wake up when new
* input comes in
- * @param screen the top-level AWT frame
+ * @param screen the top-level Swing frame
*/
- public AWTTerminal(final Object listener, final AWTScreen screen) {
+ public SwingTerminal(final Object listener, final SwingScreen screen) {
this.listener = listener;
this.screen = screen;
mouse1 = false;
}
/**
- * Pass AWT keystrokes into the event queue.
+ * Pass Swing keystrokes into the event queue.
*
* @param key keystroke received
*/
}
/**
- * Pass AWT keystrokes into the event queue.
+ * Pass Swing keystrokes into the event queue.
*
* @param key keystroke received
*/
}
/**
- * Pass AWT keystrokes into the event queue.
+ * Pass Swing keystrokes into the event queue.
*
* @param key keystroke received
*/
shift = key.isShiftDown();
/*
- System.err.printf("AWT Key: %s\n", key);
+ 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);
import java.awt.Insets;
/**
- * AWTSessionInfo provides a session implementation with a callback into an
- * AWT Frame to support queryWindowSize(). The username is blank, language
+ * 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.
*/
-public final class AWTSessionInfo implements SessionInfo {
+public final class SwingSessionInfo implements SessionInfo {
/**
- * The AWT Frame.
+ * The Swing Frame.
*/
private Frame frame;
/**
* Public constructor.
*
- * @param frame the AWT Frame
+ * @param frame the Swing Frame
* @param textWidth the width of a cell in pixels
* @param textHeight the height of a cell in pixels
*/
- public AWTSessionInfo(final Frame frame, final int textWidth,
+ public SwingSessionInfo(final Frame frame, final int textWidth,
final int textHeight) {
this.frame = frame;
/**
* TTYSessionInfo queries environment variables and the tty window size for
- * the session information. The username is taken from
- * getpwuid(geteuid()).pw_name, language is taken from LANG, and text window
- * size from ioctl(TIOCGWINSIZ).
+ * the session information. The username is taken from user.name, language
+ * is taken from user.language, and text window size from 'stty size'.
*/
public final class TTYSessionInfo implements SessionInfo {