cjk support wip
[fanfix.git] / src / jexer / backend / ECMA48Terminal.java
index 2afa81a35500e2a99c17c6500c0b748a5f922926..ca1d499594734c75275a53f90a7f5fb8aa9f9a56 100644 (file)
@@ -44,17 +44,17 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
-import java.util.LinkedList;
-import java.util.Map;
 
 import jexer.TImage;
 import jexer.bits.Cell;
 import jexer.bits.CellAttributes;
 import jexer.bits.Color;
+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.*;
 
 /**
@@ -86,6 +86,8 @@ public class ECMA48Terminal extends LogicalScreen
      * 1024.
      */
     private static final int MAX_COLOR_REGISTERS = 1024;
+    // Black-and-white is possible too.
+    // private static final int MAX_COLOR_REGISTERS = 2;
 
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
@@ -343,6 +345,16 @@ public class ECMA48Terminal extends LogicalScreen
             int green = (color >>>  8) & 0xFF;
             int blue  =  color         & 0xFF;
 
+            if (MAX_COLOR_REGISTERS == 2) {
+                if (((red * red) + (green * green) + (blue * blue)) < 35568) {
+                    // Black
+                    return 0;
+                }
+                // White
+                return 1;
+            }
+
+
             rgbToHsl(red, green, blue, hsl);
             int hue = hsl[0];
             int sat = hsl[1];
@@ -480,9 +492,9 @@ public class ECMA48Terminal extends LogicalScreen
                     int red, green, blue;
                     if (imageX < image.getWidth() - 1) {
                         int pXpY  = ditheredImage.getRGB(imageX + 1, imageY);
-                        red   = (int) ((pXpY >>> 16) & 0xFF) + (7 * redError);
-                        green = (int) ((pXpY >>>  8) & 0xFF) + (7 * greenError);
-                        blue  = (int) ( pXpY         & 0xFF) + (7 * blueError);
+                        red   = ((pXpY >>> 16) & 0xFF) + (7 * redError);
+                        green = ((pXpY >>>  8) & 0xFF) + (7 * greenError);
+                        blue  = ( pXpY         & 0xFF) + (7 * blueError);
                         red = clamp(red);
                         green = clamp(green);
                         blue = clamp(blue);
@@ -493,9 +505,9 @@ public class ECMA48Terminal extends LogicalScreen
                         if (imageY < image.getHeight() - 1) {
                             int pXpYp = ditheredImage.getRGB(imageX + 1,
                                 imageY + 1);
-                            red   = (int) ((pXpYp >>> 16) & 0xFF) + redError;
-                            green = (int) ((pXpYp >>>  8) & 0xFF) + greenError;
-                            blue  = (int) ( pXpYp         & 0xFF) + blueError;
+                            red   = ((pXpYp >>> 16) & 0xFF) + redError;
+                            green = ((pXpYp >>>  8) & 0xFF) + greenError;
+                            blue  = ( pXpYp         & 0xFF) + blueError;
                             red = clamp(red);
                             green = clamp(green);
                             blue = clamp(blue);
@@ -509,9 +521,9 @@ public class ECMA48Terminal extends LogicalScreen
                         int pXYp  = ditheredImage.getRGB(imageX,
                             imageY + 1);
 
-                        red   = (int) ((pXmYp >>> 16) & 0xFF) + (3 * redError);
-                        green = (int) ((pXmYp >>>  8) & 0xFF) + (3 * greenError);
-                        blue  = (int) ( pXmYp         & 0xFF) + (3 * blueError);
+                        red   = ((pXmYp >>> 16) & 0xFF) + (3 * redError);
+                        green = ((pXmYp >>>  8) & 0xFF) + (3 * greenError);
+                        blue  = ( pXmYp         & 0xFF) + (3 * blueError);
                         red = clamp(red);
                         green = clamp(green);
                         blue = clamp(blue);
@@ -519,9 +531,9 @@ public class ECMA48Terminal extends LogicalScreen
                         pXmYp |= ((green & 0xFF) << 8) | (blue & 0xFF);
                         ditheredImage.setRGB(imageX - 1, imageY + 1, pXmYp);
 
-                        red   = (int) ((pXYp >>> 16) & 0xFF) + (5 * redError);
-                        green = (int) ((pXYp >>>  8) & 0xFF) + (5 * greenError);
-                        blue  = (int) ( pXYp         & 0xFF) + (5 * blueError);
+                        red   = ((pXYp >>> 16) & 0xFF) + (5 * redError);
+                        green = ((pXYp >>>  8) & 0xFF) + (5 * greenError);
+                        blue  = ( pXYp         & 0xFF) + (5 * blueError);
                         red = clamp(red);
                         green = clamp(green);
                         blue = clamp(blue);
@@ -663,6 +675,14 @@ public class ECMA48Terminal extends LogicalScreen
             // map the BufferedImage colors to their nearest neighbor in RGB
             // space.
 
+            if (MAX_COLOR_REGISTERS == 2) {
+                rgbColors.add(0);
+                rgbColors.add(0xFFFFFF);
+                rgbSortedIndex[0] = 0;
+                rgbSortedIndex[1] = 1;
+                return;
+            }
+
             // We build a palette using the Hue-Saturation-Luminence model,
             // with 5+ bits for Hue, 2+ bits for Saturation, and 1+ bit for
             // Luminance.  We convert these colors to 24-bit RGB, sort them
@@ -982,7 +1002,8 @@ public class ECMA48Terminal extends LogicalScreen
     // ------------------------------------------------------------------------
 
     /**
-     * Constructor sets up state for getEvent().
+     * Constructor sets up state for getEvent().  If either windowWidth or
+     * windowHeight are less than 1, the terminal is not resized.
      *
      * @param listener the object this backend needs to wake up when new
      * input comes in
@@ -1006,10 +1027,12 @@ public class ECMA48Terminal extends LogicalScreen
 
         // Send dtterm/xterm sequences, which will probably not work because
         // allowWindowOps is defaulted to false.
-        String resizeString = String.format("\033[8;%d;%dt", windowHeight,
-            windowWidth);
-        this.output.write(resizeString);
-        this.output.flush();
+        if ((windowWidth > 0) && (windowHeight > 0)) {
+            String resizeString = String.format("\033[8;%d;%dt", windowHeight,
+                windowWidth);
+            this.output.write(resizeString);
+            this.output.flush();
+        }
     }
 
     /**
@@ -1088,7 +1111,7 @@ public class ECMA48Terminal extends LogicalScreen
         reloadOptions();
 
         // Spin up the input reader
-        eventQueue = new LinkedList<TInputEvent>();
+        eventQueue = new ArrayList<TInputEvent>();
         readerThread = new Thread(this);
         readerThread.start();
 
@@ -1174,7 +1197,7 @@ public class ECMA48Terminal extends LogicalScreen
         reloadOptions();
 
         // Spin up the input reader
-        eventQueue = new LinkedList<TInputEvent>();
+        eventQueue = new ArrayList<TInputEvent>();
         readerThread = new Thread(this);
         readerThread.start();
 
@@ -1238,6 +1261,19 @@ public class ECMA48Terminal extends LogicalScreen
         flush();
     }
 
+    /**
+     * Resize the physical screen to match the logical screen dimensions.
+     */
+    @Override
+    public void resizeToScreen() {
+        // Send dtterm/xterm sequences, which will probably not work because
+        // allowWindowOps is defaulted to false.
+        String resizeString = String.format("\033[8;%d;%dt", getHeight(),
+            getWidth());
+        this.output.write(resizeString);
+        this.output.flush();
+    }
+
     // ------------------------------------------------------------------------
     // TerminalReader ---------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -1358,7 +1394,7 @@ public class ECMA48Terminal extends LogicalScreen
         // available() will often return > 1, so we need to read in chunks to
         // stay caught up.
         char [] readBuffer = new char[128];
-        List<TInputEvent> events = new LinkedList<TInputEvent>();
+        List<TInputEvent> events = new ArrayList<TInputEvent>();
 
         while (!done && !stopReaderThread) {
             try {
@@ -1423,6 +1459,11 @@ public class ECMA48Terminal extends LogicalScreen
                         events.clear();
                     }
 
+                    if (output.checkError()) {
+                        // This is EOF.
+                        done = true;
+                    }
+
                     // Wait 20 millis for more data
                     Thread.sleep(20);
                 }
@@ -1434,6 +1475,17 @@ public class ECMA48Terminal extends LogicalScreen
                 done = true;
             }
         } // while ((done == false) && (stopReaderThread == false))
+
+        // Pass an event up to TApplication to tell it this Backend is done.
+        synchronized (eventQueue) {
+            eventQueue.add(new TCommandEvent(cmBackendDisconnect));
+        }
+        if (listener != null) {
+            synchronized (listener) {
+                listener.notifyAll();
+            }
+        }
+
         // System.err.println("*** run() exiting..."); System.err.flush();
     }
 
@@ -2213,6 +2265,9 @@ public class ECMA48Terminal extends LogicalScreen
         // Check for new window size
         long windowSizeDelay = nowTime - windowSizeTime;
         if (windowSizeDelay > 1000) {
+            int oldTextWidth = getTextWidth();
+            int oldTextHeight = getTextHeight();
+
             sessionInfo.queryWindowSize();
             int newWidth = sessionInfo.getWindowWidth();
             int newHeight = sessionInfo.getWindowHeight();
@@ -2221,14 +2276,24 @@ public class ECMA48Terminal extends LogicalScreen
                 || (newHeight != windowResize.getHeight())
             ) {
 
+                // Request xterm report window dimensions in pixels again.
+                // Between now and then, ensure that the reported text cell
+                // size is the same by setting widthPixels and heightPixels
+                // to match the new dimensions.
+                widthPixels = oldTextWidth * newWidth;
+                heightPixels = oldTextHeight * newHeight;
+
                 if (debugToStderr) {
                     System.err.println("Screen size changed, old size " +
                         windowResize);
                     System.err.println("                     new size " +
                         newWidth + " x " + newHeight);
+                    System.err.println("                   old pixels " +
+                        oldTextWidth + " x " + oldTextHeight);
+                    System.err.println("                   new pixels " +
+                        getTextWidth() + " x " + getTextHeight());
                 }
 
-                // Request xterm report window dimensions in pixels again.
                 this.output.printf("%s", xtermReportWindowPixelDimensions());
                 this.output.flush();
 
@@ -2789,18 +2854,45 @@ public class ECMA48Terminal extends LogicalScreen
 
         int [] rgbArray;
         for (int i = 0; i < cells.size() - 1; i++) {
-            if (cells.get(i).isInvertedImage()) {
+            int tileWidth = Math.min(cells.get(i).getImage().getWidth(),
+                imageWidth);
+            int tileHeight = Math.min(cells.get(i).getImage().getHeight(),
+                imageHeight);
+            if (false && cells.get(i).isInvertedImage()) {
+                // I used to put an all-white cell over the cursor, don't do
+                // that anymore.
                 rgbArray = new int[imageWidth * imageHeight];
                 for (int j = 0; j < rgbArray.length; j++) {
                     rgbArray[j] = 0xFFFFFF;
                 }
             } else {
-                rgbArray = cells.get(i).getImage().getRGB(0, 0,
-                    imageWidth, imageHeight, null, 0, imageWidth);
+                try {
+                    rgbArray = cells.get(i).getImage().getRGB(0, 0,
+                        tileWidth, tileHeight, null, 0, tileWidth);
+                } catch (Exception e) {
+                    throw new RuntimeException("image " + imageWidth + "x" +
+                        imageHeight +
+                        "tile " + tileWidth + "x" +
+                        tileHeight +
+                        " cells.get(i).getImage() " +
+                        cells.get(i).getImage() +
+                        " i " + i +
+                        " fullWidth " + fullWidth +
+                        " fullHeight " + fullHeight, e);
+                }
             }
-            image.setRGB(i * imageWidth, 0, imageWidth, imageHeight,
-                rgbArray, 0, imageWidth);
-            if (imageHeight < fullHeight) {
+
+            /*
+            System.err.printf("calling image.setRGB(): %d %d %d %d %d\n",
+                i * imageWidth, 0, imageWidth, imageHeight,
+                0, imageWidth);
+            System.err.printf("   fullWidth %d fullHeight %d cells.size() %d textWidth %d\n",
+                fullWidth, fullHeight, cells.size(), getTextWidth());
+             */
+
+            image.setRGB(i * imageWidth, 0, tileWidth, tileHeight,
+                rgbArray, 0, tileWidth);
+            if (tileHeight < fullHeight) {
                 int backgroundColor = cells.get(i).getBackground().getRGB();
                 for (int imageX = 0; imageX < image.getWidth(); imageX++) {
                     for (int imageY = imageHeight; imageY < fullHeight;
@@ -2812,14 +2904,22 @@ public class ECMA48Terminal extends LogicalScreen
             }
         }
         totalWidth -= ((cells.size() - 1) * imageWidth);
-        if (cells.get(cells.size() - 1).isInvertedImage()) {
+        if (false && cells.get(cells.size() - 1).isInvertedImage()) {
+            // I used to put an all-white cell over the cursor, don't do that
+            // anymore.
             rgbArray = new int[totalWidth * imageHeight];
             for (int j = 0; j < rgbArray.length; j++) {
                 rgbArray[j] = 0xFFFFFF;
             }
         } else {
-            rgbArray = cells.get(cells.size() - 1).getImage().getRGB(0, 0,
-                totalWidth, imageHeight, null, 0, totalWidth);
+            try {
+                rgbArray = cells.get(cells.size() - 1).getImage().getRGB(0, 0,
+                    totalWidth, imageHeight, null, 0, totalWidth);
+            } catch (Exception e) {
+                throw new RuntimeException("image " + imageWidth + "x" +
+                    imageHeight + " cells.get(cells.size() - 1).getImage() " +
+                    cells.get(cells.size() - 1).getImage(), e);
+            }
         }
         image.setRGB((cells.size() - 1) * imageWidth, 0, totalWidth,
             imageHeight, rgbArray, 0, totalWidth);
@@ -2887,6 +2987,8 @@ public class ECMA48Terminal extends LogicalScreen
                 // colored pixels, and select the color.
                 sb.append(String.format("$#%d", i));
 
+                int oldData = -1;
+                int oldDataCount = 0;
                 for (int imageX = 0; imageX < image.getWidth(); imageX++) {
 
                     // Add up all the pixels that match this color.
@@ -2919,10 +3021,32 @@ public class ECMA48Terminal extends LogicalScreen
                         }
                     }
                     assert (data >= 0);
-                    assert (data < 127);
+                    assert (data < 64);
                     data += 63;
-                    sb.append((char) data);
+
+                    if (data == oldData) {
+                        oldDataCount++;
+                    } else {
+                        if (oldDataCount == 1) {
+                            sb.append((char) oldData);
+                        } else if (oldDataCount > 1) {
+                            sb.append(String.format("!%d", oldDataCount));
+                            sb.append((char) oldData);
+                        }
+                        oldDataCount = 1;
+                        oldData = data;
+                    }
+
                 } // for (int imageX = 0; imageX < image.getWidth(); imageX++)
+
+                // Emit the last sequence.
+                if (oldDataCount == 1) {
+                    sb.append((char) oldData);
+                } else if (oldDataCount > 1) {
+                    sb.append(String.format("!%d", oldDataCount));
+                    sb.append((char) oldData);
+                }
+
             } // for (int i = 0; i < MAX_COLOR_REGISTERS; i++)
 
             // Advance to the next scan line.
@@ -2941,6 +3065,15 @@ public class ECMA48Terminal extends LogicalScreen
         return (startSixel(x, y) + sb.toString() + endSixel());
     }
 
+    /**
+     * Get the sixel support flag.
+     *
+     * @return true if this terminal is emitting sixel
+     */
+    public boolean hasSixel() {
+        return sixel;
+    }
+
     // ------------------------------------------------------------------------
     // End sixel output support -----------------------------------------------
     // ------------------------------------------------------------------------
@@ -3385,6 +3518,11 @@ public class ECMA48Terminal extends LogicalScreen
      *
      * Note that this also sets the alternate/primary screen buffer.
      *
+     * Finally, also emit a Privacy Message sequence that Jexer recognizes to
+     * mean "hide the mouse pointer."  We have to use our own sequence to do
+     * this because there is no standard in xterm for unilaterally hiding the
+     * pointer all the time (regardless of typing).
+     *
      * @param on If true, enable mouse report and use the alternate screen
      * buffer.  If false disable mouse reporting and use the primary screen
      * buffer.
@@ -3392,9 +3530,9 @@ public class ECMA48Terminal extends LogicalScreen
      */
     private String mouse(final boolean on) {
         if (on) {
-            return "\033[?1002;1003;1005;1006h\033[?1049h";
+            return "\033[?1002;1003;1005;1006h\033[?1049h\033^hideMousePointer\033\\";
         }
-        return "\033[?1002;1003;1006;1005l\033[?1049l";
+        return "\033[?1002;1003;1006;1005l\033[?1049l\033^showMousePointer\033\\";
     }
 
 }