LICENSE CHANGED TO MIT
[nikiroo-utils.git] / src / jexer / TTerminalWindow.java
index 58e60e567c15387ffd82379f696af8e1f303ff5a..8730dfe7a8f98eadcc4df7af8ed9a76f89d7b17c 100644 (file)
@@ -1,29 +1,27 @@
-/**
+/*
  * Jexer - Java Text User Interface
  *
- * License: LGPLv3 or later
- *
- * This module is licensed under the GNU Lesser General Public License
- * Version 3.  Please see the file "COPYING" in this directory for more
- * information about the GNU Lesser General Public License Version 3.
+ * The MIT License (MIT)
  *
- *     Copyright (C) 2015  Kevin Lamonte
+ * Copyright (C) 2016 Kevin Lamonte
  *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 3 of
- * the License, or (at your option) any later version.
+ * 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:
  *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
  *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, see
- * http://www.gnu.org/licenses/, or write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA
+ * 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
@@ -36,6 +34,7 @@ import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import jexer.bits.Cell;
 import jexer.bits.CellAttributes;
@@ -79,6 +78,9 @@ public class TTerminalWindow extends TWindow {
 
         super(application, "Terminal", x, y, 80 + 2, 24 + 2, flags);
 
+        // Assume XTERM
+        ECMA48.DeviceType deviceType = ECMA48.DeviceType.XTERM;
+
         try {
             String [] cmdShellWindows = {
                 "cmd.exe"
@@ -91,19 +93,21 @@ public class TTerminalWindow extends TWindow {
                 "script", "-fqe", "/dev/null"
             };
             // Spawn a shell and pass its I/O to the other constructor.
+
             ProcessBuilder pb;
             if (System.getProperty("os.name").startsWith("Windows")) {
                 pb = new ProcessBuilder(cmdShellWindows);
             } else {
                 pb = new ProcessBuilder(cmdShell);
             }
-            // shell = Runtime.getRuntime().exec(cmdShell);
-
-            // TODO: add LANG, TERM, LINES, and COLUMNS
+            Map<String, String> env = pb.environment();
+            env.put("TERM", ECMA48.deviceTypeTerm(deviceType));
+            env.put("LANG", ECMA48.deviceTypeLang(deviceType, "en"));
+            env.put("COLUMNS", "80");
+            env.put("LINES", "24");
             pb.redirectErrorStream(true);
             shell = pb.start();
-            emulator = new ECMA48(ECMA48.DeviceType.XTERM,
-                shell.getInputStream(),
+            emulator = new ECMA48(deviceType, shell.getInputStream(),
                 shell.getOutputStream());
         } catch (IOException e) {
             e.printStackTrace();
@@ -161,18 +165,13 @@ public class TTerminalWindow extends TWindow {
             List<DisplayLine> display = emulator.getDisplayBuffer();
 
             // Put together the visible rows
-            // System.err.printf("----------------------------\n");
-            // System.err.printf("vScroller.value %d\n", vScroller.getValue());
             int visibleHeight = getHeight() - 2;
-            // System.err.printf("visibleHeight %d\n", visibleHeight);
             int visibleBottom = scrollback.size() + display.size()
                 + vScroller.getValue();
-            // System.err.printf("visibleBottom %d\n", visibleBottom);
             assert (visibleBottom >= 0);
 
             List<DisplayLine> preceedingBlankLines = new LinkedList<DisplayLine>();
             int visibleTop = visibleBottom - visibleHeight;
-            // System.err.printf("visibleTop %d\n", visibleTop);
             if (visibleTop < 0) {
                 for (int i = visibleTop; i < 0; i++) {
                     preceedingBlankLines.add(emulator.getBlankDisplayLine());
@@ -184,16 +183,13 @@ public class TTerminalWindow extends TWindow {
             List<DisplayLine> displayLines = new LinkedList<DisplayLine>();
             displayLines.addAll(scrollback);
             displayLines.addAll(display);
-            // System.err.printf("displayLines.size %d\n", displayLines.size());
 
             List<DisplayLine> visibleLines = new LinkedList<DisplayLine>();
             visibleLines.addAll(preceedingBlankLines);
             visibleLines.addAll(displayLines.subList(visibleTop,
                     visibleBottom));
-            // System.err.printf("visibleLines.size %d\n", visibleLines.size());
 
             visibleHeight -= visibleLines.size();
-            // System.err.printf("visibleHeight %d\n", visibleHeight);
             assert (visibleHeight >= 0);
 
             // Now draw the emulator screen
@@ -210,7 +206,7 @@ public class TTerminalWindow extends TWindow {
                     Cell ch = line.charAt(i);
                     Cell newCell = new Cell();
                     newCell.setTo(ch);
-                    boolean reverse = line.isReverseColor() ^ ch.getReverse();
+                    boolean reverse = line.isReverseColor() ^ ch.isReverse();
                     newCell.setReverse(false);
                     if (reverse) {
                         newCell.setBackColor(ch.getForeColor());
@@ -244,7 +240,12 @@ public class TTerminalWindow extends TWindow {
      * Handle window close.
      */
     @Override public void onClose() {
-        emulator.close();
+        if (shell != null) {
+            shell.destroy();
+            shell = null;
+        } else {
+            emulator.close();
+        }
     }
 
     /**
@@ -262,12 +263,12 @@ public class TTerminalWindow extends TWindow {
             if (vScroller != null) {
                 setCursorY(getCursorY() - vScroller.getValue());
             }
-            setHasCursor(emulator.visibleCursor());
+            setCursorVisible(emulator.isCursorVisible());
             if (getCursorX() > getWidth() - 2) {
-                setHasCursor(false);
+                setCursorVisible(false);
             }
             if ((getCursorY() > getHeight() - 2) || (getCursorY() < 0)) {
-                setHasCursor(false);
+                setCursorVisible(false);
             }
             if (emulator.getScreenTitle().length() > 0) {
                 // Only update the title if the shell is still alive
@@ -275,15 +276,21 @@ public class TTerminalWindow extends TWindow {
                     setTitle(emulator.getScreenTitle());
                 }
             }
-            setMaximumWindowWidth(emulator.getWidth() + 2);
 
             // Check to see if the shell has died.
             if (!emulator.isReading() && (shell != null)) {
-                // The emulator exited on its own, all is fine
-                setTitle(String.format("%s [Completed - %d]",
-                        getTitle(), shell.exitValue()));
-                shell = null;
-                emulator.close();
+                try {
+                    int rc = shell.exitValue();
+                    // The emulator exited on its own, all is fine
+                    setTitle(String.format("%s [Completed - %d]",
+                            getTitle(), rc));
+                    shell = null;
+                    emulator.close();
+                } catch (IllegalThreadStateException e) {
+                    // The emulator thread has exited, but the shell Process
+                    // hasn't figured that out yet.  Do nothing, we will see
+                    // this in a future tick.
+                }
             } else if (emulator.isReading() && (shell != null)) {
                 // The shell might be dead, let's check
                 try {
@@ -355,6 +362,31 @@ public class TTerminalWindow extends TWindow {
         } // synchronized (emulator)
     }
 
+    /**
+     * Check if a mouse press/release/motion event coordinate is over the
+     * emulator.
+     *
+     * @param mouse a mouse-based event
+     * @return whether or not the mouse is on the emulator
+     */
+    private final boolean mouseOnEmulator(final TMouseEvent mouse) {
+
+        synchronized (emulator) {
+            if (!emulator.isReading()) {
+                return false;
+            }
+        }
+
+        if ((mouse.getAbsoluteX() >= getAbsoluteX() + 1)
+            && (mouse.getAbsoluteX() <  getAbsoluteX() + getWidth() - 1)
+            && (mouse.getAbsoluteY() >= getAbsoluteY() + 1)
+            && (mouse.getAbsoluteY() <  getAbsoluteY() + getHeight() - 1)
+        ) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Handle keystrokes.
      *
@@ -411,18 +443,86 @@ public class TTerminalWindow extends TWindow {
      */
     @Override
     public void onMouseDown(final TMouseEvent mouse) {
+        if (inWindowMove || inWindowResize) {
+            // TWindow needs to deal with this.
+            super.onMouseDown(mouse);
+            return;
+        }
 
-        if (mouse.getMouseWheelUp()) {
+        if (mouse.isMouseWheelUp()) {
             vScroller.decrement();
             return;
         }
-        if (mouse.getMouseWheelDown()) {
+        if (mouse.isMouseWheelDown()) {
             vScroller.increment();
             return;
         }
+        if (mouseOnEmulator(mouse)) {
+            synchronized (emulator) {
+                mouse.setX(mouse.getX() - 1);
+                mouse.setY(mouse.getY() - 1);
+                emulator.mouse(mouse);
+                readEmulatorState();
+                return;
+            }
+        }
 
-        // Pass to children
+        // Emulator didn't consume it, pass it on
         super.onMouseDown(mouse);
     }
 
+    /**
+     * Handle mouse release events.
+     *
+     * @param mouse mouse button release event
+     */
+    @Override
+    public void onMouseUp(final TMouseEvent mouse) {
+        if (inWindowMove || inWindowResize) {
+            // TWindow needs to deal with this.
+            super.onMouseUp(mouse);
+            return;
+        }
+
+        if (mouseOnEmulator(mouse)) {
+            synchronized (emulator) {
+                mouse.setX(mouse.getX() - 1);
+                mouse.setY(mouse.getY() - 1);
+                emulator.mouse(mouse);
+                readEmulatorState();
+                return;
+            }
+        }
+
+        // Emulator didn't consume it, pass it on
+        super.onMouseUp(mouse);
+    }
+
+    /**
+     * Handle mouse motion events.
+     *
+     * @param mouse mouse motion event
+     */
+    @Override
+    public void onMouseMotion(final TMouseEvent mouse) {
+        if (inWindowMove || inWindowResize) {
+            // TWindow needs to deal with this.
+            super.onMouseMotion(mouse);
+            return;
+        }
+
+        if (mouseOnEmulator(mouse)) {
+            synchronized (emulator) {
+                mouse.setX(mouse.getX() - 1);
+                mouse.setY(mouse.getY() - 1);
+                emulator.mouse(mouse);
+                readEmulatorState();
+                return;
+            }
+        }
+
+        // Emulator didn't consume it, pass it on
+        super.onMouseMotion(mouse);
+    }
+
 }