#16 Refactor Swing backend, demo of multiple TApplications in one Swing frame
authorKevin Lamonte <kevin.lamonte@gmail.com>
Mon, 7 Aug 2017 19:53:57 +0000 (15:53 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Mon, 7 Aug 2017 19:53:57 +0000 (15:53 -0400)
27 files changed:
docs/TODO.md
docs/worklog.md
src/jexer/TApplication.java
src/jexer/TWidget.java
src/jexer/TWindow.java
src/jexer/backend/Backend.java
src/jexer/backend/ECMA48Backend.java
src/jexer/backend/ECMA48Terminal.java [moved from src/jexer/io/ECMA48Terminal.java with 82% similarity]
src/jexer/backend/GenericBackend.java [new file with mode: 0644]
src/jexer/backend/LogicalScreen.java [moved from src/jexer/io/Screen.java with 98% similarity]
src/jexer/backend/Screen.java [new file with mode: 0644]
src/jexer/backend/SessionInfo.java [moved from src/jexer/session/SessionInfo.java with 98% similarity]
src/jexer/backend/SwingBackend.java
src/jexer/backend/SwingComponent.java [new file with mode: 0644]
src/jexer/backend/SwingSessionInfo.java [moved from src/jexer/session/SwingSessionInfo.java with 74% similarity]
src/jexer/backend/SwingTerminal.java [new file with mode: 0644]
src/jexer/backend/TSessionInfo.java [moved from src/jexer/session/TSessionInfo.java with 99% similarity]
src/jexer/backend/TTYSessionInfo.java [moved from src/jexer/session/TTYSessionInfo.java with 99% similarity]
src/jexer/backend/TerminalReader.java [moved from src/jexer/session/package-info.java with 63% similarity]
src/jexer/demos/Demo5.java [new file with mode: 0644]
src/jexer/demos/DemoApplication.java
src/jexer/io/ECMA48Screen.java [deleted file]
src/jexer/io/SwingScreen.java [deleted file]
src/jexer/io/SwingTerminal.java [deleted file]
src/jexer/io/TimeoutInputStream.java
src/jexer/io/package-info.java
src/jexer/net/TelnetInputStream.java

index 3f4df474c38e07da1879910d21be702139b08008..64ec85439cb10c7023199ab9041addf132238b20 100644 (file)
@@ -10,6 +10,10 @@ BUG: TTreeView.reflow() doesn't keep the vertical dot within the
 
 0.0.5
 
+- Multiscreen support:
+  - cmAbort to cmScreenDisconnected
+  - cmScreenConnected
+
 - TApplication
   - getAllWindows()
   - Expose menu management functions (addMenu, getMenu, getAllMenus,
@@ -39,12 +43,6 @@ BUG: TTreeView.reflow() doesn't keep the vertical dot within the
 
 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
index a169b68cddb6d99c9386a40052d1694f33548df7..2573fb2cd6ae5701841b21ad0fca893213810bd4 100644 (file)
@@ -1,6 +1,50 @@
 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
index 3907a9d15bda19c2bb636a0fe79e165012eb3f3b..c783d8d087237f129e2171ad967be60619ed550d 100644 (file)
@@ -51,16 +51,18 @@ import jexer.event.TMenuEvent;
 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 {
 
@@ -908,6 +910,13 @@ public class TApplication implements Runnable {
     // Main loop --------------------------------------------------------------
     // ------------------------------------------------------------------------
 
+    /**
+     * Force this application to exit.
+     */
+    public void exit() {
+        quit = true;
+    }
+
     /**
      * Run this application until it exits.
      */
index 5be209e33b44ea79f4df2d85c49c454301e53bd0..726e13714ea4395af2c75fcf409ba685f24c4fbb 100644 (file)
@@ -32,6 +32,7 @@ import java.io.IOException;
 import java.util.List;
 import java.util.ArrayList;
 
+import jexer.backend.Screen;
 import jexer.bits.ColorTheme;
 import jexer.event.TCommandEvent;
 import jexer.event.TInputEvent;
@@ -39,7 +40,6 @@ import jexer.event.TKeypressEvent;
 import jexer.event.TMenuEvent;
 import jexer.event.TMouseEvent;
 import jexer.event.TResizeEvent;
-import jexer.io.Screen;
 import jexer.menu.TMenu;
 import static jexer.TKeypress.*;
 
index 8749eb86cd1b46f4d41db781400d688cc4aba9de..8032fdc3d676a9adc4f1988bec84e3a03a75d891 100644 (file)
@@ -30,6 +30,7 @@ package jexer;
 
 import java.util.HashSet;
 
+import jexer.backend.Screen;
 import jexer.bits.Cell;
 import jexer.bits.CellAttributes;
 import jexer.bits.GraphicsChars;
@@ -38,7 +39,6 @@ import jexer.event.TKeypressEvent;
 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.*;
index 664dff954f2ba322c06f3e3928dd32c4605ed07e..2793ab00fa0eef73740b055cdd87f04dc8f92ace 100644 (file)
@@ -31,69 +31,54 @@ package jexer.backend;
 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);
 
 }
index 24357bb11841aad529379bd718313d1924c24297..ee7a10374f0332ec05139a5f4bf42cf7f51580ea 100644 (file)
@@ -36,14 +36,12 @@ import java.io.UnsupportedEncodingException;
 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.
@@ -74,12 +72,8 @@ public final class ECMA48Backend extends Backend {
         // 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;
     }
 
     /**
@@ -107,12 +101,8 @@ public final class ECMA48Backend extends Backend {
         // 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;
     }
 
     /**
@@ -157,7 +147,7 @@ public final class ECMA48Backend extends Backend {
      */
     @Override
     public void shutdown() {
-        terminal.shutdown();
+        terminal.closeTerminal();
     }
 
     /**
@@ -167,7 +157,7 @@ public final class ECMA48Backend extends Backend {
      */
     @Override
     public void setTitle(final String title) {
-        ((ECMA48Screen) screen).setTitle(title);
+        screen.setTitle(title);
     }
 
 }
similarity index 82%
rename from src/jexer/io/ECMA48Terminal.java
rename to src/jexer/backend/ECMA48Terminal.java
index 0b1ce111181532ccfc0234c660aea8632c227d89..13704151df3037585b4d4d0f442593444a880834 100644 (file)
@@ -26,7 +26,7 @@
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
  */
-package jexer.io;
+package jexer.backend;
 
 import java.io.BufferedReader;
 import java.io.FileDescriptor;
@@ -44,21 +44,26 @@ import java.util.Date;
 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
@@ -294,7 +299,7 @@ public final class ECMA48Terminal implements Runnable {
     public ECMA48Terminal(final Object listener, final InputStream input,
         final OutputStream output) throws UnsupportedEncodingException {
 
-        reset();
+        resetParser();
         mouse1           = false;
         mouse2           = false;
         mouse3           = false;
@@ -352,6 +357,14 @@ public final class ECMA48Terminal implements Runnable {
         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();
     }
 
     /**
@@ -381,7 +394,7 @@ public final class ECMA48Terminal implements Runnable {
         if (writer == null) {
             throw new IllegalArgumentException("Writer must be specified");
         }
-        reset();
+        resetParser();
         mouse1           = false;
         mouse2           = false;
         mouse3           = false;
@@ -431,6 +444,14 @@ public final class ECMA48Terminal implements Runnable {
         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();
     }
 
     /**
@@ -453,7 +474,7 @@ public final class ECMA48Terminal implements Runnable {
     /**
      * Restore terminal to normal state.
      */
-    public void shutdown() {
+    public void closeTerminal() {
 
         // System.err.println("=== shutdown() ==="); System.err.flush();
 
@@ -498,10 +519,243 @@ public final class ECMA48Terminal implements Runnable {
         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();
@@ -860,7 +1114,7 @@ public final class ECMA48Terminal implements Runnable {
             if (escDelay > 100) {
                 // After 0.1 seconds, assume a true escape character
                 queue.add(controlChar((char)0x1B, false));
-                reset();
+                resetParser();
             }
         }
     }
@@ -926,7 +1180,7 @@ public final class ECMA48Terminal implements Runnable {
             if (escDelay > 250) {
                 // After 0.25 seconds, assume a true escape character
                 events.add(controlChar((char)0x1B, false));
-                reset();
+                resetParser();
             }
         }
 
@@ -949,7 +1203,7 @@ public final class ECMA48Terminal implements Runnable {
             if (ch <= 0x1F) {
                 // Control character
                 events.add(controlChar(ch, false));
-                reset();
+                resetParser();
                 return;
             }
 
@@ -957,7 +1211,7 @@ public final class ECMA48Terminal implements Runnable {
                 // Normal character
                 events.add(new TKeypressEvent(false, 0, ch,
                         false, false, false));
-                reset();
+                resetParser();
                 return;
             }
 
@@ -967,7 +1221,7 @@ public final class ECMA48Terminal implements Runnable {
             if (ch <= 0x1F) {
                 // ALT-Control character
                 events.add(controlChar(ch, true));
-                reset();
+                resetParser();
                 return;
             }
 
@@ -989,7 +1243,7 @@ public final class ECMA48Terminal implements Runnable {
             }
             alt = true;
             events.add(new TKeypressEvent(false, 0, ch, alt, ctrl, shift));
-            reset();
+            resetParser();
             return;
 
         case ESCAPE_INTERMEDIATE:
@@ -1011,12 +1265,12 @@ public final class ECMA48Terminal implements Runnable {
                 default:
                     break;
                 }
-                reset();
+                resetParser();
                 return;
             }
 
             // Unknown keystroke, ignore
-            reset();
+            resetParser();
             return;
 
         case CSI_ENTRY:
@@ -1038,37 +1292,37 @@ public final class ECMA48Terminal implements Runnable {
                 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
@@ -1084,7 +1338,7 @@ public final class ECMA48Terminal implements Runnable {
             }
 
             // Unknown keystroke, ignore
-            reset();
+            resetParser();
             return;
 
         case MOUSE_SGR:
@@ -1107,7 +1361,7 @@ public final class ECMA48Terminal implements Runnable {
                 if (event != null) {
                     events.add(event);
                 }
-                reset();
+                resetParser();
                 return;
             case 'm':
                 // Generate a mouse release event
@@ -1115,14 +1369,14 @@ public final class ECMA48Terminal implements Runnable {
                 if (event != null) {
                     events.add(event);
                 }
-                reset();
+                resetParser();
                 return;
             default:
                 break;
             }
 
             // Unknown keystroke, ignore
-            reset();
+            resetParser();
             return;
 
         case CSI_PARAM:
@@ -1141,7 +1395,7 @@ public final class ECMA48Terminal implements Runnable {
 
             if (ch == '~') {
                 events.add(csiFnKey());
-                reset();
+                resetParser();
                 return;
             }
 
@@ -1155,7 +1409,7 @@ public final class ECMA48Terminal implements Runnable {
                         ctrl = csiIsCtrl(params.get(1));
                     }
                     events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
-                    reset();
+                    resetParser();
                     return;
                 case 'B':
                     // Down
@@ -1165,7 +1419,7 @@ public final class ECMA48Terminal implements Runnable {
                         ctrl = csiIsCtrl(params.get(1));
                     }
                     events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
-                    reset();
+                    resetParser();
                     return;
                 case 'C':
                     // Right
@@ -1175,7 +1429,7 @@ public final class ECMA48Terminal implements Runnable {
                         ctrl = csiIsCtrl(params.get(1));
                     }
                     events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
-                    reset();
+                    resetParser();
                     return;
                 case 'D':
                     // Left
@@ -1185,7 +1439,7 @@ public final class ECMA48Terminal implements Runnable {
                         ctrl = csiIsCtrl(params.get(1));
                     }
                     events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
-                    reset();
+                    resetParser();
                     return;
                 case 'H':
                     // Home
@@ -1195,7 +1449,7 @@ public final class ECMA48Terminal implements Runnable {
                         ctrl = csiIsCtrl(params.get(1));
                     }
                     events.add(new TKeypressEvent(kbHome, alt, ctrl, shift));
-                    reset();
+                    resetParser();
                     return;
                 case 'F':
                     // End
@@ -1205,7 +1459,7 @@ public final class ECMA48Terminal implements Runnable {
                         ctrl = csiIsCtrl(params.get(1));
                     }
                     events.add(new TKeypressEvent(kbEnd, alt, ctrl, shift));
-                    reset();
+                    resetParser();
                     return;
                 default:
                     break;
@@ -1213,7 +1467,7 @@ public final class ECMA48Terminal implements Runnable {
             }
 
             // Unknown keystroke, ignore
-            reset();
+            resetParser();
             return;
 
         case MOUSE:
@@ -1221,7 +1475,7 @@ public final class ECMA48Terminal implements Runnable {
             if (params.get(0).length() == 3) {
                 // We have enough to generate a mouse event
                 events.add(parseMouse());
-                reset();
+                resetParser();
             }
             return;
 
@@ -1249,19 +1503,17 @@ public final class ECMA48Terminal implements Runnable {
     }
 
     /**
-     * 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
@@ -1269,7 +1521,7 @@ public final class ECMA48Terminal implements Runnable {
      * @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);
@@ -1389,7 +1641,7 @@ public final class ECMA48Terminal implements Runnable {
 
     /**
      * 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
@@ -1397,7 +1649,7 @@ public final class ECMA48Terminal implements Runnable {
      * @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);
@@ -1434,8 +1686,7 @@ public final class ECMA48Terminal implements Runnable {
     /**
      * 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
@@ -1446,7 +1697,7 @@ public final class ECMA48Terminal implements Runnable {
      * @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) {
 
@@ -1498,13 +1749,12 @@ public final class ECMA48Terminal implements Runnable {
     }
 
     /**
-     * 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);
     }
 
@@ -1524,13 +1774,12 @@ public final class ECMA48Terminal implements Runnable {
     }
 
     /**
-     * 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";
@@ -1548,29 +1797,29 @@ public final class ECMA48Terminal implements Runnable {
      *
      * @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);
     }
 
diff --git a/src/jexer/backend/GenericBackend.java b/src/jexer/backend/GenericBackend.java
new file mode 100644 (file)
index 0000000..d96f7a9
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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);
+
+}
similarity index 98%
rename from src/jexer/io/Screen.java
rename to src/jexer/backend/LogicalScreen.java
index 85e121f29be7a5a1d111bc44da9778bc85691dbb..8432e4eae526f8a74f395303ebb8fbd3d5f3c09e 100644 (file)
  * @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.
@@ -551,7 +550,7 @@ public abstract class Screen {
     /**
      * Public constructor.  Sets everything to not-bold, white-on-black.
      */
-    protected Screen() {
+    protected LogicalScreen() {
         offsetX  = 0;
         offsetY  = 0;
         width    = 80;
@@ -747,10 +746,9 @@ public abstract class Screen {
     }
 
     /**
-     * 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).
@@ -772,4 +770,12 @@ public abstract class Screen {
     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) {}
+
 }
diff --git a/src/jexer/backend/Screen.java b/src/jexer/backend/Screen.java
new file mode 100644 (file)
index 0000000..9b5890f
--- /dev/null
@@ -0,0 +1,355 @@
+/*
+ * 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);
+
+}
similarity index 98%
rename from src/jexer/session/SessionInfo.java
rename to src/jexer/backend/SessionInfo.java
index 0712b6a2ab7a62500950c47221137c6435d665df..5c2d0342eed427c36e21d62c19ded0074dddd8ad 100644 (file)
@@ -26,7 +26,7 @@
  * @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
index acca7fa0b66c0e4d056e8b268d504b11de83f731..876015e4d125874591dcf5da5bc32cfd694dd207 100644 (file)
 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.
@@ -56,7 +55,7 @@ public final class SwingBackend extends Backend {
     }
 
     /**
-     * Public constructor.
+     * Public constructor will spawn a new JFrame.
      *
      * @param listener the object this backend needs to wake up when new
      * input comes in
@@ -68,16 +67,40 @@ public final class SwingBackend extends Backend {
     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;
     }
 
     /**
@@ -105,7 +128,7 @@ public final class SwingBackend extends Backend {
      */
     @Override
     public void shutdown() {
-        ((SwingScreen) screen).shutdown();
+        terminal.closeTerminal();
     }
 
     /**
@@ -115,7 +138,17 @@ public final class SwingBackend extends Backend {
      */
     @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);
     }
 
 }
diff --git a/src/jexer/backend/SwingComponent.java b/src/jexer/backend/SwingComponent.java
new file mode 100644 (file)
index 0000000..84e1472
--- /dev/null
@@ -0,0 +1,438 @@
+/*
+ * 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);
+        }
+    }
+
+}
similarity index 74%
rename from src/jexer/session/SwingSessionInfo.java
rename to src/jexer/backend/SwingSessionInfo.java
index dcba27da5dc37a2778e4aa02ad5eb5483dab058d..7df751d8edc53a8e7bc9727d01490f7a58e44c3e 100644 (file)
  * @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.
@@ -127,40 +126,49 @@ public final class SwingSessionInfo implements SessionInfo {
         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);
-         */
+        */
 
     }
 
diff --git a/src/jexer/backend/SwingTerminal.java b/src/jexer/backend/SwingTerminal.java
new file mode 100644 (file)
index 0000000..c2fbfb4
--- /dev/null
@@ -0,0 +1,1886 @@
+/*
+ * 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();
+        }
+    }
+
+}
similarity index 99%
rename from src/jexer/session/TSessionInfo.java
rename to src/jexer/backend/TSessionInfo.java
index adfdfd51b37b4017ce2e41e97fd3568009167411..880098d7aa5e07ddb11f18fdc782f4722c70e770 100644 (file)
@@ -26,7 +26,7 @@
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
  */
-package jexer.session;
+package jexer.backend;
 
 /**
  * TSessionInfo provides a default session implementation.  The username is
similarity index 99%
rename from src/jexer/session/TTYSessionInfo.java
rename to src/jexer/backend/TTYSessionInfo.java
index 5d20b6b3861423faace55c813005291d00afad96..e69fc7018664b4c25515040c55aedc46d2501c04 100644 (file)
@@ -26,7 +26,7 @@
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
  */
-package jexer.session;
+package jexer.backend;
 
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
similarity index 63%
rename from src/jexer/session/package-info.java
rename to src/jexer/backend/TerminalReader.java
index 0b6ccf0fa22fec8d4e00ed5754db718579b0e6e5..48e4043f92b34bab0b208bd3e6453e26d5024360 100644 (file)
  * @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();
+
+}
diff --git a/src/jexer/demos/Demo5.java b/src/jexer/demos/Demo5.java
new file mode 100644 (file)
index 0000000..88d6a7c
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * 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();
+        }
+    }
+
+}
index a352dee8ef625655f828faf435a9122efd878fc7..43bb709020c45a22777bf047128528ba94026133 100644 (file)
@@ -34,6 +34,8 @@ import java.util.*;
 import jexer.*;
 import jexer.event.*;
 import jexer.menu.*;
+import jexer.backend.Backend;
+import jexer.backend.SwingTerminal;
 
 /**
  * The demo application itself.
@@ -73,6 +75,12 @@ public class DemoApplication extends TApplication {
         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();
     }
@@ -133,6 +141,17 @@ public class DemoApplication extends TApplication {
         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.
      *
@@ -143,6 +162,21 @@ public class DemoApplication extends TApplication {
     @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;
diff --git a/src/jexer/io/ECMA48Screen.java b/src/jexer/io/ECMA48Screen.java
deleted file mode 100644 (file)
index 9daf6de..0000000
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * 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();
-    }
-
-}
diff --git a/src/jexer/io/SwingScreen.java b/src/jexer/io/SwingScreen.java
deleted file mode 100644 (file)
index ee8467d..0000000
+++ /dev/null
@@ -1,997 +0,0 @@
-/*
- * 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);
-    }
-
-}
diff --git a/src/jexer/io/SwingTerminal.java b/src/jexer/io/SwingTerminal.java
deleted file mode 100644 (file)
index 07d1ffa..0000000
+++ /dev/null
@@ -1,721 +0,0 @@
-/*
- * 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();
-        }
-    }
-
-}
index d540c60fb2137d392b001f18579082129db80cb6..db9eb4a6006897ea1a576c123cea0df0acf23a68 100644 (file)
@@ -73,7 +73,7 @@ public class TimeoutInputStream extends InputStream {
     }
 
     /**
-     * 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
index 04cbc5923f9047a935f36a3c1e3a6b6dfb71d665..4c04cf10d9723521b6c9caf187fe10a835542181 100644 (file)
@@ -28,6 +28,6 @@
  */
 
 /**
- * User-facing I/O, including screen, keyboard, and mouse handling classes.
+ * java.io subclasses.
  */
 package jexer.io;
index 2a5ed4dfbf33491174d7affe22f4067eb8f473b6..ea30171c30d38a47a4acbf13e035739db6a48424 100644 (file)
@@ -34,7 +34,7 @@ import java.util.ArrayList;
 import java.util.Map;
 import java.util.TreeMap;
 
-import jexer.session.SessionInfo;
+import jexer.backend.SessionInfo;
 import static jexer.net.TelnetSocket.*;
 
 /**