retrofit
[fanfix.git] / src / jexer / backend / SwingTerminal.java
index 314e6f5a89e6854dc354e0cd70afac6531cbafc6..aa49467372f143a785166e46cc15a7899f81a9c6 100644 (file)
@@ -36,6 +36,7 @@ import java.awt.Graphics2D;
 import java.awt.Graphics;
 import java.awt.Insets;
 import java.awt.Rectangle;
+import java.awt.RenderingHints;
 import java.awt.Toolkit;
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
@@ -117,7 +118,12 @@ public class SwingTerminal extends LogicalScreen
         /**
          * Use an outlined block for the cursor.
          */
-        OUTLINE
+        OUTLINE,
+
+        /**
+         * Use a vertical bar for the cursor.
+         */
+        VERTICAL_BAR,
     }
 
     // ------------------------------------------------------------------------
@@ -182,12 +188,12 @@ public class SwingTerminal extends LogicalScreen
     /**
      * Width of a character cell in pixels.
      */
-    private int textWidth = 1;
+    private int textWidth = 16;
 
     /**
      * Height of a character cell in pixels.
      */
-    private int textHeight = 1;
+    private int textHeight = 20;
 
     /**
      * Width of a character cell in pixels, as reported by font.
@@ -301,6 +307,13 @@ public class SwingTerminal extends LogicalScreen
     // Constructors -----------------------------------------------------------
     // ------------------------------------------------------------------------
 
+    /**
+     * Static constructor.
+     */
+    static {
+        setDOSColors();
+    }
+
     /**
      * Public constructor creates a new JFrame to render to.
      *
@@ -316,7 +329,6 @@ public class SwingTerminal extends LogicalScreen
 
         this.fontSize = fontSize;
 
-        setDOSColors();
         reloadOptions();
 
         try {
@@ -397,7 +409,7 @@ public class SwingTerminal extends LogicalScreen
                     SwingTerminal.this.setDimensions(sessionInfo.
                         getWindowWidth(), sessionInfo.getWindowHeight());
 
-                    SwingTerminal.this.resizeToScreen();
+                    SwingTerminal.this.resizeToScreen(true);
                     SwingTerminal.this.swing.setVisible(true);
                 }
             });
@@ -438,7 +450,6 @@ public class SwingTerminal extends LogicalScreen
 
         this.fontSize = fontSize;
 
-        setDOSColors();
         reloadOptions();
 
         try {
@@ -567,8 +578,20 @@ public class SwingTerminal extends LogicalScreen
             && (swing.getBufferStrategy() != null)
         ) {
             do {
-                clearPhysical();
                 do {
+                    /*
+                     * TODO:
+                     *
+                     * Under Windows and Mac (I think?), there was a problem
+                     * with the screen not updating on the initial load.
+                     * Adding clearPhysical() below "fixed" it, but at a
+                     * horrible performance penalty on Linux which I am no
+                     * longer willing to accept.
+                     *
+                     * Fix this in the "right" way for Windows/OSX such that
+                     * the entire screen does not require a full redraw.
+                     */
+                    // clearPhysical();
                     drawToSwing();
                 } while (swing.getBufferStrategy().contentsRestored());
 
@@ -643,6 +666,8 @@ public class SwingTerminal extends LogicalScreen
             cursorStyle = CursorStyle.OUTLINE;
         } else if (cursorStyleString.equals("block")) {
             cursorStyle = CursorStyle.BLOCK;
+        } else if (cursorStyleString.equals("verticalbar")) {
+            cursorStyle = CursorStyle.VERTICAL_BAR;
         }
 
         // Pull the system property for triple buffering.
@@ -653,6 +678,9 @@ public class SwingTerminal extends LogicalScreen
         } else {
             SwingComponent.tripleBuffer = false;
         }
+
+        // Set custom colors
+        setCustomSystemColors();
     }
 
     // ------------------------------------------------------------------------
@@ -704,6 +732,62 @@ public class SwingTerminal extends LogicalScreen
         dosColors = true;
     }
 
+    /**
+     * Setup Swing colors to match those provided in system properties.
+     */
+    private static void setCustomSystemColors() {
+        synchronized (SwingTerminal.class) {
+            MYBLACK   = getCustomColor("jexer.Swing.color0", MYBLACK);
+            MYRED     = getCustomColor("jexer.Swing.color1", MYRED);
+            MYGREEN   = getCustomColor("jexer.Swing.color2", MYGREEN);
+            MYYELLOW  = getCustomColor("jexer.Swing.color3", MYYELLOW);
+            MYBLUE    = getCustomColor("jexer.Swing.color4", MYBLUE);
+            MYMAGENTA = getCustomColor("jexer.Swing.color5", MYMAGENTA);
+            MYCYAN    = getCustomColor("jexer.Swing.color6", MYCYAN);
+            MYWHITE   = getCustomColor("jexer.Swing.color7", MYWHITE);
+            MYBOLD_BLACK   = getCustomColor("jexer.Swing.color8", MYBOLD_BLACK);
+            MYBOLD_RED     = getCustomColor("jexer.Swing.color9", MYBOLD_RED);
+            MYBOLD_GREEN   = getCustomColor("jexer.Swing.color10", MYBOLD_GREEN);
+            MYBOLD_YELLOW  = getCustomColor("jexer.Swing.color11", MYBOLD_YELLOW);
+            MYBOLD_BLUE    = getCustomColor("jexer.Swing.color12", MYBOLD_BLUE);
+            MYBOLD_MAGENTA = getCustomColor("jexer.Swing.color13", MYBOLD_MAGENTA);
+            MYBOLD_CYAN    = getCustomColor("jexer.Swing.color14", MYBOLD_CYAN);
+            MYBOLD_WHITE   = getCustomColor("jexer.Swing.color15", MYBOLD_WHITE);
+        }
+    }
+
+    /**
+     * Setup one Swing color to match the RGB value provided in system
+     * properties.
+     *
+     * @param key the system property key
+     * @param defaultColor the default color to return if key is not set, or
+     * incorrect
+     * @return a color from the RGB string, or defaultColor
+     */
+    private static Color getCustomColor(final String key,
+        final Color defaultColor) {
+
+        String rgb = System.getProperty(key);
+        if (rgb == null) {
+            return defaultColor;
+        }
+        if (rgb.startsWith("#")) {
+            rgb = rgb.substring(1);
+        }
+        int rgbInt = 0;
+        try {
+            rgbInt = Integer.parseInt(rgb, 16);
+        } catch (NumberFormatException e) {
+            return defaultColor;
+        }
+        Color color = new Color((rgbInt & 0xFF0000) >>> 16,
+            (rgbInt & 0x00FF00) >>> 8,
+            (rgbInt & 0x0000FF));
+
+        return color;
+    }
+
     /**
      * Get the number of millis to wait before switching the blink from
      * visible to invisible.
@@ -715,6 +799,15 @@ public class SwingTerminal extends LogicalScreen
         return blinkMillis;
     }
 
+    /**
+     * Get the current status of the blink flag.
+     *
+     * @return true if the cursor and blinking text should be visible
+     */
+    public boolean getCursorBlinkVisible() {
+        return cursorBlinkVisible;
+    }
+
     /**
      * Get the font size in points.
      *
@@ -741,13 +834,35 @@ public class SwingTerminal extends LogicalScreen
      * @param font the new font
      */
     public void setFont(final Font font) {
-        synchronized (this) {
-            this.font = font;
-            getFontDimensions();
-            swing.setFont(font);
-            glyphCacheBlink = new HashMap<Cell, BufferedImage>();
-            glyphCache = new HashMap<Cell, BufferedImage>();
-            resizeToScreen();
+        if (!SwingUtilities.isEventDispatchThread()) {
+            // Not in the Swing thread: force this inside the Swing thread.
+            try {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                    public void run() {
+                        synchronized (this) {
+                            SwingTerminal.this.font = font;
+                            getFontDimensions();
+                            swing.setFont(font);
+                            glyphCacheBlink = new HashMap<Cell, BufferedImage>();
+                            glyphCache = new HashMap<Cell, BufferedImage>();
+                            resizeToScreen(true);
+                        }
+                    }
+                });
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            } catch (java.lang.reflect.InvocationTargetException e) {
+                e.printStackTrace();
+            }
+        } else {
+            synchronized (this) {
+                SwingTerminal.this.font = font;
+                getFontDimensions();
+                swing.setFont(font);
+                glyphCacheBlink = new HashMap<Cell, BufferedImage>();
+                glyphCache = new HashMap<Cell, BufferedImage>();
+                resizeToScreen(true);
+            }
         }
     }
 
@@ -1096,13 +1211,24 @@ public class SwingTerminal extends LogicalScreen
         gotFontDimensions = true;
     }
 
+    /**
+     * Resize the physical screen to match the logical screen dimensions.
+     *
+     * @param resizeComponent if true, resize the Swing component
+     */
+    private void resizeToScreen(final boolean resizeComponent) {
+        if (resizeComponent) {
+            swing.setDimensions(textWidth * width, textHeight * height);
+        }
+        clearPhysical();
+    }
+
     /**
      * Resize the physical screen to match the logical screen dimensions.
      */
     @Override
     public void resizeToScreen() {
-        swing.setDimensions(textWidth * width, textHeight * height);
-        clearPhysical();
+        resizeToScreen(false);
     }
 
     /**
@@ -1125,15 +1251,26 @@ public class SwingTerminal extends LogicalScreen
 
         // Draw the background rectangle, then the foreground character.
         assert (cell.isImage());
+
+        // Enable anti-aliasing
+        if (gr instanceof Graphics2D) {
+            ((Graphics2D) gr).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+            ((Graphics2D) gr).setRenderingHint(RenderingHints.KEY_RENDERING,
+                RenderingHints.VALUE_RENDER_QUALITY);
+        }
+
         gr.setColor(cell.getBackground());
         gr.fillRect(xPixel, yPixel, textWidth, textHeight);
 
         BufferedImage image = cell.getImage();
         if (image != null) {
             if (swing.getFrame() != null) {
-                gr.drawImage(image, xPixel, yPixel, swing.getFrame());
+                gr.drawImage(image, xPixel, yPixel, getTextWidth(),
+                    getTextHeight(), swing.getFrame());
             } else {
-                gr.drawImage(image, xPixel, yPixel, swing.getComponent());
+                gr.drawImage(image, xPixel, yPixel,  getTextWidth(),
+                    getTextHeight(),swing.getComponent());
             }
             return;
         }
@@ -1155,7 +1292,7 @@ public class SwingTerminal extends LogicalScreen
         /*
         System.err.println("drawGlyph(): " + xPixel + " " + yPixel +
             " " + cell);
-        */
+         */
 
         BufferedImage image = null;
         if (cell.isBlink() && !cursorBlinkVisible) {
@@ -1187,8 +1324,7 @@ public class SwingTerminal extends LogicalScreen
             gr2 = (Graphics2D) gr;
         }
 
-        Cell cellColor = new Cell();
-        cellColor.setTo(cell);
+        Cell cellColor = new Cell(cell);
 
         // Check for reverse
         if (cell.isReverse()) {
@@ -1196,18 +1332,26 @@ public class SwingTerminal extends LogicalScreen
             cellColor.setBackColor(cell.getForeColor());
         }
 
+        // Enable anti-aliasing
+        if (gr instanceof Graphics2D) {
+            ((Graphics2D) gr).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+            ((Graphics2D) gr).setRenderingHint(RenderingHints.KEY_RENDERING,
+                RenderingHints.VALUE_RENDER_QUALITY);
+        }
+
         // 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,
+            char [] chars = Character.toChars(cell.getChar());
+            gr2.drawChars(chars, 0, chars.length, gr2x + textAdjustX,
                 gr2y + textHeight - maxDescent + textAdjustY);
 
             if (cell.isUnderline()) {
@@ -1220,8 +1364,7 @@ public class SwingTerminal extends LogicalScreen
 
             // We need a new key that will not be mutated by
             // invertCell().
-            Cell key = new Cell();
-            key.setTo(cell);
+            Cell key = new Cell(cell);
             if (cell.isBlink() && !cursorBlinkVisible) {
                 glyphCacheBlink.put(key, image);
             } else {
@@ -1254,18 +1397,34 @@ public class SwingTerminal extends LogicalScreen
             int xPixel = cursorX * textWidth + left;
             int yPixel = cursorY * textHeight + top;
             Cell lCell = logical[cursorX][cursorY];
+            int cursorWidth = textWidth;
+            switch (lCell.getWidth()) {
+            case SINGLE:
+                // NOP
+                break;
+            case LEFT:
+                cursorWidth *= 2;
+                break;
+            case RIGHT:
+                cursorWidth *= 2;
+                xPixel -= textWidth;
+                break;
+            }
             gr.setColor(attrToForegroundColor(lCell));
             switch (cursorStyle) {
             default:
                 // Fall through...
             case UNDERLINE:
-                gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
+                gr.fillRect(xPixel, yPixel + textHeight - 2, cursorWidth, 2);
                 break;
             case BLOCK:
-                gr.fillRect(xPixel, yPixel, textWidth, textHeight);
+                gr.fillRect(xPixel, yPixel, cursorWidth, textHeight);
                 break;
             case OUTLINE:
-                gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
+                gr.drawRect(xPixel, yPixel, cursorWidth - 1, textHeight - 1);
+                break;
+            case VERTICAL_BAR:
+                gr.fillRect(xPixel, yPixel, 2, textHeight);
                 break;
             }
         }
@@ -1313,7 +1472,7 @@ public class SwingTerminal extends LogicalScreen
         if (bounds != null) {
             // Only update what is in the bounds
             xCellMin = textColumn(bounds.x);
-            xCellMax = textColumn(bounds.x + bounds.width);
+            xCellMax = textColumn(bounds.x + bounds.width) + 1;
             if (xCellMax > width) {
                 xCellMax = width;
             }
@@ -1324,7 +1483,7 @@ public class SwingTerminal extends LogicalScreen
                 xCellMin = 0;
             }
             yCellMin = textRow(bounds.y);
-            yCellMax = textRow(bounds.y + bounds.height);
+            yCellMax = textRow(bounds.y + bounds.height) + 1;
             if (yCellMax > height) {
                 yCellMax = height;
             }
@@ -1346,7 +1505,7 @@ public class SwingTerminal extends LogicalScreen
             /*
             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++) {
@@ -1793,6 +1952,7 @@ public class SwingTerminal extends LogicalScreen
                 break;
             default:
                 if (!alt && ctrl && !shift) {
+                    // Control character, replace ch with 'A', 'B', etc.
                     ch = KeyEvent.getKeyText(key.getKeyCode()).charAt(0);
                 }
                 // Not a special key, put it together
@@ -1974,6 +2134,10 @@ public class SwingTerminal extends LogicalScreen
         boolean eventMouse1 = false;
         boolean eventMouse2 = false;
         boolean eventMouse3 = false;
+        boolean eventAlt = false;
+        boolean eventCtrl = false;
+        boolean eventShift = false;
+
         if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
             eventMouse1 = true;
         }
@@ -1983,6 +2147,16 @@ public class SwingTerminal extends LogicalScreen
         if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
             eventMouse3 = true;
         }
+        if ((modifiers & MouseEvent.ALT_DOWN_MASK) != 0) {
+            eventAlt = true;
+        }
+        if ((modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) {
+            eventCtrl = true;
+        }
+        if ((modifiers & MouseEvent.SHIFT_DOWN_MASK) != 0) {
+            eventShift = true;
+        }
+
         mouse1 = eventMouse1;
         mouse2 = eventMouse2;
         mouse3 = eventMouse3;
@@ -1990,7 +2164,8 @@ public class SwingTerminal extends LogicalScreen
         int y = textRow(mouse.getY());
 
         TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
-            x, y, x, y, mouse1, mouse2, mouse3, false, false);
+            x, y, x, y, mouse1, mouse2, mouse3, false, false,
+            eventAlt, eventCtrl, eventShift);
 
         synchronized (eventQueue) {
             eventQueue.add(mouseEvent);
@@ -2018,8 +2193,24 @@ public class SwingTerminal extends LogicalScreen
         oldMouseX = x;
         oldMouseY = y;
 
+        boolean eventAlt = false;
+        boolean eventCtrl = false;
+        boolean eventShift = false;
+
+        int modifiers = mouse.getModifiersEx();
+        if ((modifiers & MouseEvent.ALT_DOWN_MASK) != 0) {
+            eventAlt = true;
+        }
+        if ((modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) {
+            eventCtrl = true;
+        }
+        if ((modifiers & MouseEvent.SHIFT_DOWN_MASK) != 0) {
+            eventShift = true;
+        }
+
         TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
-            x, y, x, y, mouse1, mouse2, mouse3, false, false);
+            x, y, x, y, mouse1, mouse2, mouse3, false, false,
+            eventAlt, eventCtrl, eventShift);
 
         synchronized (eventQueue) {
             eventQueue.add(mouseEvent);
@@ -2051,7 +2242,7 @@ public class SwingTerminal extends LogicalScreen
      * @param mouse mouse event received
      */
     public void mouseEntered(final MouseEvent mouse) {
-        // Ignore
+        swing.requestFocusInWindow();
     }
 
     /**
@@ -2073,6 +2264,10 @@ public class SwingTerminal extends LogicalScreen
         boolean eventMouse1 = false;
         boolean eventMouse2 = false;
         boolean eventMouse3 = false;
+        boolean eventAlt = false;
+        boolean eventCtrl = false;
+        boolean eventShift = false;
+
         if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
             eventMouse1 = true;
         }
@@ -2082,6 +2277,16 @@ public class SwingTerminal extends LogicalScreen
         if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
             eventMouse3 = true;
         }
+        if ((modifiers & MouseEvent.ALT_DOWN_MASK) != 0) {
+            eventAlt = true;
+        }
+        if ((modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) {
+            eventCtrl = true;
+        }
+        if ((modifiers & MouseEvent.SHIFT_DOWN_MASK) != 0) {
+            eventShift = true;
+        }
+
         mouse1 = eventMouse1;
         mouse2 = eventMouse2;
         mouse3 = eventMouse3;
@@ -2089,7 +2294,8 @@ public class SwingTerminal extends LogicalScreen
         int y = textRow(mouse.getY());
 
         TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
-            x, y, x, y, mouse1, mouse2, mouse3, false, false);
+            x, y, x, y, mouse1, mouse2, mouse3, false, false,
+            eventAlt, eventCtrl, eventShift);
 
         synchronized (eventQueue) {
             eventQueue.add(mouseEvent);
@@ -2112,6 +2318,10 @@ public class SwingTerminal extends LogicalScreen
         boolean eventMouse1 = false;
         boolean eventMouse2 = false;
         boolean eventMouse3 = false;
+        boolean eventAlt = false;
+        boolean eventCtrl = false;
+        boolean eventShift = false;
+
         if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
             eventMouse1 = true;
         }
@@ -2121,6 +2331,16 @@ public class SwingTerminal extends LogicalScreen
         if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
             eventMouse3 = true;
         }
+        if ((modifiers & MouseEvent.ALT_DOWN_MASK) != 0) {
+            eventAlt = true;
+        }
+        if ((modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) {
+            eventCtrl = true;
+        }
+        if ((modifiers & MouseEvent.SHIFT_DOWN_MASK) != 0) {
+            eventShift = true;
+        }
+
         if (mouse1) {
             mouse1 = false;
             eventMouse1 = true;
@@ -2137,7 +2357,8 @@ public class SwingTerminal extends LogicalScreen
         int y = textRow(mouse.getY());
 
         TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP,
-            x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false);
+            x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false,
+            eventAlt, eventCtrl, eventShift);
 
         synchronized (eventQueue) {
             eventQueue.add(mouseEvent);
@@ -2166,6 +2387,10 @@ public class SwingTerminal extends LogicalScreen
         boolean eventMouse3 = false;
         boolean mouseWheelUp = false;
         boolean mouseWheelDown = false;
+        boolean eventAlt = false;
+        boolean eventCtrl = false;
+        boolean eventShift = false;
+
         if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
             eventMouse1 = true;
         }
@@ -2175,6 +2400,16 @@ public class SwingTerminal extends LogicalScreen
         if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
             eventMouse3 = true;
         }
+        if ((modifiers & MouseEvent.ALT_DOWN_MASK) != 0) {
+            eventAlt = true;
+        }
+        if ((modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) {
+            eventCtrl = true;
+        }
+        if ((modifiers & MouseEvent.SHIFT_DOWN_MASK) != 0) {
+            eventShift = true;
+        }
+
         mouse1 = eventMouse1;
         mouse2 = eventMouse2;
         mouse3 = eventMouse3;
@@ -2188,7 +2423,8 @@ public class SwingTerminal extends LogicalScreen
         }
 
         TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
-            x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown);
+            x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown,
+            eventAlt, eventCtrl, eventShift);
 
         synchronized (eventQueue) {
             eventQueue.add(mouseEvent);