#35 emoji font wip
[fanfix.git] / src / jexer / tterminal / ECMA48.java
index c94d7d2b8524f60290496f53e4b97cbe0bdec883..7ce95d6255e34bce8081bf2d54a23b6e92faddce 100644 (file)
@@ -47,10 +47,14 @@ import java.util.HashMap;
 import java.util.List;
 
 import jexer.TKeypress;
-import jexer.event.TMouseEvent;
+import jexer.backend.GlyphMaker;
 import jexer.bits.Color;
 import jexer.bits.Cell;
 import jexer.bits.CellAttributes;
+import jexer.bits.StringUtils;
+import jexer.event.TInputEvent;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
 import jexer.io.ReadTimeoutException;
 import jexer.io.TimeoutInputStream;
 import static jexer.TKeypress.*;
@@ -477,6 +481,23 @@ public class ECMA48 implements Runnable {
      */
     private int textHeight = 20;
 
+    /**
+     * The last used height of a character cell in pixels, only used for
+     * full-width chars.
+     */
+    private int lastTextHeight = -1;
+
+    /**
+     * The glyph drawer for full-width chars.
+     */
+    private GlyphMaker glyphMaker = null;
+
+    /**
+     * Input queue for keystrokes and mouse events to send to the remote
+     * side.
+     */
+    private ArrayList<TInputEvent> userQueue = new ArrayList<TInputEvent>();
+
     /**
      * DECSC/DECRC save/restore a subset of the total state.  This class
      * encapsulates those specific flags/modes.
@@ -669,12 +690,18 @@ public class ECMA48 implements Runnable {
         char [] readBufferUTF8 = null;
         byte [] readBuffer = null;
         if (utf8) {
-            readBufferUTF8 = new char[128];
+            readBufferUTF8 = new char[2048];
         } else {
-            readBuffer = new byte[128];
+            readBuffer = new byte[2048];
         }
 
         while (!done && !stopReaderThread) {
+            synchronized (userQueue) {
+                while (userQueue.size() > 0) {
+                    handleUserEvent(userQueue.remove(0));
+                }
+            }
+
             try {
                 int n = inputStream.available();
 
@@ -696,7 +723,7 @@ public class ECMA48 implements Runnable {
                 }
                 if (n == 0) {
                     try {
-                        Thread.sleep(2);
+                        Thread.sleep(10);
                     } catch (InterruptedException e) {
                         // SQUASH
                     }
@@ -797,6 +824,31 @@ public class ECMA48 implements Runnable {
     // ECMA48 -----------------------------------------------------------------
     // ------------------------------------------------------------------------
 
+    /**
+     * Process keyboard and mouse events from the user.
+     *
+     * @param event the input event to consume
+     */
+    private void handleUserEvent(final TInputEvent event) {
+        if (event instanceof TKeypressEvent) {
+            keypress(((TKeypressEvent) event).getKey());
+        }
+        if (event instanceof TMouseEvent) {
+            mouse((TMouseEvent) event);
+        }
+    }
+
+    /**
+     * Add a keyboard and mouse event from the user to the queue.
+     *
+     * @param event the input event to consume
+     */
+    public void addUserEvent(final TInputEvent event) {
+        synchronized (userQueue) {
+            userQueue.add(event);
+        }
+    }
+
     /**
      * Return the proper primary Device Attributes string.
      *
@@ -1377,6 +1429,35 @@ public class ECMA48 implements Runnable {
     private void printCharacter(final char ch) {
         int rightMargin = this.rightMargin;
 
+        if (StringUtils.width(ch) == 2) {
+            // This is a full-width character.  Save two spaces, and then
+            // draw the character as two image halves.
+            int x0 = currentState.cursorX;
+            int y0 = currentState.cursorY;
+            printCharacter(' ');
+            printCharacter(' ');
+            if ((currentState.cursorX == x0 + 2)
+                && (currentState.cursorY == y0)
+            ) {
+                // We can draw both halves of the character.
+                drawHalves(x0, y0, x0 + 1, y0, ch);
+            } else if ((currentState.cursorX == x0 + 1)
+                && (currentState.cursorY == y0)
+            ) {
+                // VT100 line wrap behavior: we should be at the right
+                // margin.  We can draw both halves of the character.
+                drawHalves(x0, y0, x0 + 1, y0, ch);
+            } else {
+                // The character splits across the line.  Draw the entire
+                // character on the new line, giving one more space for it.
+                x0 = currentState.cursorX - 1;
+                y0 = currentState.cursorY;
+                printCharacter(' ');
+                drawHalves(x0, y0, x0 + 1, y0, ch);
+            }
+            return;
+        }
+
         // Check if we have double-width, and if so chop at 40/66 instead of
         // 80/132
         if (display.get(currentState.cursorY).isDoubleWidth()) {
@@ -1427,19 +1508,22 @@ public class ECMA48 implements Runnable {
         CellAttributes newCellAttributes = (CellAttributes) newCell;
         newCellAttributes.setTo(currentState.attr);
         DisplayLine line = display.get(currentState.cursorY);
-        // Insert mode special case
-        if (insertMode == true) {
-            line.insert(currentState.cursorX, newCell);
-        } else {
-            // Replace an existing character
-            line.replace(currentState.cursorX, newCell);
-        }
 
-        // Increment horizontal
-        if (wrapLineFlag == false) {
-            currentState.cursorX++;
-            if (currentState.cursorX > rightMargin) {
-                currentState.cursorX--;
+        if (StringUtils.width(ch) == 1) {
+            // Insert mode special case
+            if (insertMode == true) {
+                line.insert(currentState.cursorX, newCell);
+            } else {
+                // Replace an existing character
+                line.replace(currentState.cursorX, newCell);
+            }
+
+            // Increment horizontal
+            if (wrapLineFlag == false) {
+                currentState.cursorX++;
+                if (currentState.cursorX > rightMargin) {
+                    currentState.cursorX--;
+                }
             }
         }
     }
@@ -1450,7 +1534,7 @@ public class ECMA48 implements Runnable {
      *
      * @param mouse mouse event received from the local user
      */
-    public void mouse(final TMouseEvent mouse) {
+    private void mouse(final TMouseEvent mouse) {
 
         /*
         System.err.printf("mouse(): protocol %s encoding %s mouse %s\n",
@@ -1598,7 +1682,7 @@ public class ECMA48 implements Runnable {
      *
      * @param keypress keypress received from the local user
      */
-    public void keypress(final TKeypress keypress) {
+    private void keypress(final TKeypress keypress) {
         writeRemote(keypressToString(keypress));
     }
 
@@ -3374,8 +3458,7 @@ public class ECMA48 implements Runnable {
      * DECALN - Screen alignment display.
      */
     private void decaln() {
-        Cell newCell = new Cell();
-        newCell.setChar('E');
+        Cell newCell = new Cell('E');
         for (DisplayLine line: display) {
             for (int i = 0; i < line.length(); i++) {
                 line.replace(i, newCell);
@@ -6806,4 +6889,42 @@ public class ECMA48 implements Runnable {
 
     }
 
+    /**
+     * Draw the left and right cells of a two-cell-wide (full-width) glyph.
+     *
+     * @param leftX the x position to draw the left half to
+     * @param leftY the y position to draw the left half to
+     * @param rightX the x position to draw the right half to
+     * @param rightY the y position to draw the right half to
+     * @param ch the character to draw
+     */
+    private void drawHalves(final int leftX, final int leftY,
+        final int rightX, final int rightY, final char ch) {
+
+        // System.err.println("drawHalves(): " + Integer.toHexString(ch));
+
+        if (lastTextHeight != textHeight) {
+            glyphMaker = GlyphMaker.getInstance(textHeight);
+            lastTextHeight = textHeight;
+        }
+
+        Cell cell = new Cell(ch, currentState.attr);
+        BufferedImage image = glyphMaker.getImage(cell, textWidth * 2,
+            textHeight);
+        BufferedImage leftImage = image.getSubimage(0, 0, textWidth,
+            textHeight);
+        BufferedImage rightImage = image.getSubimage(textWidth, 0, textWidth,
+            textHeight);
+
+        Cell left = new Cell(cell);
+        left.setImage(leftImage);
+        left.setWidth(Cell.Width.LEFT);
+        display.get(leftY).replace(leftX, left);
+
+        Cell right = new Cell(cell);
+        right.setImage(rightImage);
+        right.setWidth(Cell.Width.RIGHT);
+        display.get(rightY).replace(rightX, right);
+    }
+
 }