#16 Refactor Swing backend, demo of multiple TApplications in one Swing frame
[fanfix.git] / src / jexer / io / SwingScreen.java
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);
-    }
-
-}