Add versioned about box, set title
authorKevin Lamonte <kevin.lamonte@gmail.com>
Fri, 17 Mar 2017 18:18:42 +0000 (14:18 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Fri, 17 Mar 2017 18:18:42 +0000 (14:18 -0400)
16 files changed:
README.md
build.xml
docs/TODO.md [new file with mode: 0644]
docs/worklog.md [new file with mode: 0644]
src/jexer/TApplication.java
src/jexer/TTerminalWindow.java
src/jexer/backend/Backend.java
src/jexer/backend/ECMA48Backend.java
src/jexer/backend/SwingBackend.java
src/jexer/demos/Demo1.java
src/jexer/demos/DemoApplication.java
src/jexer/io/ECMA48Screen.java
src/jexer/io/ECMA48Terminal.java
src/jexer/io/SwingScreen.java
src/jexer/menu/TMenu.java
src/jexer/session/SwingSessionInfo.java

index ab6069ea253144c30aed456e26f10b5fda6c1b8e..2f146cc4f3b585d4678eeb3bd2c350109099ed32 100644 (file)
--- a/README.md
+++ b/README.md
@@ -211,34 +211,5 @@ ambiguous.  This section describes such issues.
 Roadmap
 -------
 
-Many tasks remain before calling this version 1.0:
-
-0.0.4
-
-- TStatusBar
-- TEditor
-- TWindow
-  - "Smart placement" for new windows
-
-0.0.5: BUG HUNT
-
-- Swing performance is better, triple-buffering appears to have helped.
-
-0.1.0: BETA RELEASE
-
-- TSpinner
-- TComboBox
-- TCalendar
-
-Wishlist features (2.0):
-
-- TTerminal
-  - Handle resize events (pass to child process)
-- Screen
-  - Allow complex characters in putCharXY() and detect them in putStringXY().
-- Drag and drop
-  - TEditor
-  - TField
-  - TText
-  - TTerminal
-  - TComboBox
+Many tasks remain before calling this version 1.0.  See docs/TODO.md
+for the complete list of tasks.
index 45014d9566523d1ab5735279e220fb8b26685bd5..5894ef15422bd2f1921a2032b2e7abb7cf88101f 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -32,6 +32,7 @@
 
   <!-- <property name="build.compiler" value="gcj"/> -->
 
+  <property name="version"       value="0.0.4"/>
   <property name="src.dir"       value="src"/>
   <property name="resources.dir" value="resources"/>
   <property name="build.dir"     value="build"/>
@@ -62,6 +63,7 @@
       <fileset dir="${src.dir}"/>
       <manifest>
        <attribute name="Main-Class" value="jexer.demos.Demo1"/>
+       <attribute name="Implementation-Version" value="${version}"/>
       </manifest>
     </jar>
   </target>
diff --git a/docs/TODO.md b/docs/TODO.md
new file mode 100644 (file)
index 0000000..47f07d0
--- /dev/null
@@ -0,0 +1,95 @@
+Jexer TODO List
+===============
+
+
+Roadmap
+-------
+
+0.0.4
+
+- TStatusBar
+  - TMenu version
+  - TWindow version
+  - Click mouse
+
+- TWindow
+  - "Smart placement" for new windows
+
+0.0.5
+
+- TEditor
+
+0.0.6
+
+- TSpinner
+- TComboBox
+- TCalendar
+
+0.0.7
+
+- THelpWindow
+  - TText + clickable links
+  - Index
+
+0.1.0: BETA RELEASE and BUG HUNT
+
+- Verify vttest in multiple tterminals.
+
+1.0.0
+
+
+
+1.1.0 Wishlist
+--------------
+
+- TTerminal
+  - Handle resize events (pass to child process)
+
+- Screen
+  - Allow complex characters in putCharXY() and detect them in putStringXY().
+
+- Drag and drop
+  - TEditor
+  - TField
+  - TText
+  - TTerminal
+  - TComboBox
+
+
+
+Regression Checklist
+--------------------
+
+  TTerminal
+    No hang when closing, whether or not script is running
+    No dead script children lying around
+    vttest passing
+
+
+
+Release Checklist √
+-------------------
+
+Fix all marked TODOs in code
+
+Eliminate DEBUG, System.err prints
+
+Version in:
+
+Update written by date to current year:
+    All code headers
+    VERSION
+
+Tag github
+
+Upload to SF
+
+
+
+Brainstorm Wishlist
+-------------------
+
+
+
+Bugs Noted In Other Programs
+----------------------------
diff --git a/docs/worklog.md b/docs/worklog.md
new file mode 100644 (file)
index 0000000..89cbef5
--- /dev/null
@@ -0,0 +1,26 @@
+Jexer Work Log
+==============
+
+March 17, 2017
+
+Jexer is coming back to active development status.  I had a lot of
+other projects ahead of it in the queue, mostly Qodem but also Jermit
+and of course lots of actual day job work keeping me too tired for
+afterhours stuff.  But here we are now, and I want to get Jexer to its
+1.0.0 release before the end of 2018.  After that it will be a
+critical bit of function for IWP and NIB, if I ever get those going.
+I need to re-organize the demo app a bit so that it fits within 80x25,
+and then get to TStatusBar.
+
+A status bar will be an optional part of TWindow.  If it exists, then
+it will be drawn last by TApplication and get events routed to it from
+TWindow's event handlers.  This will have the nice effect that the
+status bar can change depending on which window is active, without any
+real extra work on TApplication's part.
+
+Putting together a proper TODO now, with release and regression
+checklists.  I think I will see if jexer is available at SourceForge,
+and if so grab it.  Perhaps I can put together some good Turbo Vision
+resources too.  At the very least direct people to the Borland-derived
+C++ releases and Free Vision.
+
index 971f6f8fa572ff243645975966243dc9ff25f598..91d2e98d7c039da4aeb9d01c84bb33385f762772 100644 (file)
@@ -370,6 +370,15 @@ public class TApplication implements Runnable {
      */
     private Backend backend;
 
+    /**
+     * Get the Backend.
+     *
+     * @return the Backend
+     */
+    public final Backend getBackend() {
+        return backend;
+    }
+
     /**
      * Get the Screen.
      *
@@ -1514,6 +1523,15 @@ public class TApplication implements Runnable {
         return false;
     }
 
+    /**
+     * Display the about dialog.
+     */
+    protected void showAboutDialog() {
+        messageBox("About", "Jexer Version " +
+            this.getClass().getPackage().getImplementationVersion(),
+            TMessageBox.Type.OK);
+    }
+
     /**
      * Method that TApplication subclasses can override to handle menu
      * events.
@@ -1549,6 +1567,10 @@ public class TApplication implements Runnable {
             closeAllWindows();
             return true;
         }
+        if (menu.getId() == TMenu.MID_ABOUT) {
+            showAboutDialog();
+            return true;
+        }
         return false;
     }
 
@@ -1752,6 +1774,24 @@ public class TApplication implements Runnable {
         return windowMenu;
     }
 
+    /**
+     * Convenience function to add a default "Help" menu.
+     *
+     * @return the new menu
+     */
+    public final TMenu addHelpMenu() {
+        TMenu helpMenu = addMenu("&Help");
+        helpMenu.addDefaultItem(TMenu.MID_HELP_CONTENTS);
+        helpMenu.addDefaultItem(TMenu.MID_HELP_INDEX);
+        helpMenu.addDefaultItem(TMenu.MID_HELP_SEARCH);
+        helpMenu.addDefaultItem(TMenu.MID_HELP_PREVIOUS);
+        helpMenu.addDefaultItem(TMenu.MID_HELP_HELP);
+        helpMenu.addDefaultItem(TMenu.MID_HELP_ACTIVE_FILE);
+        helpMenu.addSeparator();
+        helpMenu.addDefaultItem(TMenu.MID_ABOUT);
+        return helpMenu;
+    }
+
     /**
      * Close all open windows.
      */
index 6d97531aabbdb76e4d896127d284c3766a7e16f5..c0af74bafa12ce9046277f0eab0746c7571bee43 100644 (file)
@@ -32,6 +32,7 @@ import java.io.InputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -158,17 +159,27 @@ public class TTerminalWindow extends TWindow {
 
             // You cannot run a login shell in a bare Process interactively,
             // due to libc's behavior of buffering when stdin/stdout aren't a
-            // tty.  Use 'script' instead to run a shell in a pty.
-            String [] cmdShell = {
+            // tty.  Use 'script' instead to run a shell in a pty.  And
+            // because BSD and GNU differ on the '-f' vs '-F' flags, we need
+            // two different commands.  Lovely.
+            String [] cmdShellGNU = {
                 "script", "-fqe", "/dev/null"
             };
+            String [] cmdShellBSD = {
+                "script", "-qe", "-F", "/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 if (System.getProperty("os.name").startsWith("Mac")) {
+                pb = new ProcessBuilder(cmdShellBSD);
+            } else if (System.getProperty("os.name").startsWith("Linux")) {
+                pb = new ProcessBuilder(cmdShellGNU);
             } else {
-                pb = new ProcessBuilder(cmdShell);
+                // When all else fails, assume GNU.
+                pb = new ProcessBuilder(cmdShellGNU);
             }
             Map<String, String> env = pb.environment();
             env.put("TERM", ECMA48.deviceTypeTerm(deviceType));
@@ -191,6 +202,39 @@ public class TTerminalWindow extends TWindow {
         addShortcutKeys();
     }
 
+    /**
+     * Terminate the child of the 'script' process used on POSIX.  This may
+     * or may not work.
+     */
+    private void terminateShellChildProcess() {
+        int pid = -1;
+        if (shell.getClass().getName().equals("java.lang.UNIXProcess")) {
+            /* get the PID on unix/linux systems */
+            try {
+                Field field = shell.getClass().getDeclaredField("pid");
+                field.setAccessible(true);
+                pid = field.getInt(shell);
+            } catch (Throwable e) {
+                // SQUASH, this didn't work.  Just bail out quietly.
+                return;
+            }
+        }
+        if (pid != -1) {
+            // shell.destroy() works successfully at killing this side of
+            // 'script'.  But we need to make sure the other side (child
+            // process) is also killed.
+            String [] cmdKillIt = {
+                "pkill", "-P", Integer.toString(pid)
+            };
+            try {
+                Runtime.getRuntime().exec(cmdKillIt);
+            } catch (Throwable e) {
+                // SQUASH, this didn't work.  Just bail out quietly.
+                return;
+            }
+        }
+    }
+
     /**
      * Public constructor.
      *
@@ -317,7 +361,7 @@ public class TTerminalWindow extends TWindow {
     @Override public void onClose() {
         emulator.close();
         if (shell != null) {
-            // System.err.println("shell.destroy()");
+            terminateShellChildProcess();
             shell.destroy();
             shell = null;
         }
index bb29b578c5387040257e209e4be58a836a6d24d6..dbc7c973359ba6c2a0b2eed7579465eb55d3bbe9 100644 (file)
@@ -89,4 +89,11 @@ public abstract class Backend {
      */
     public abstract void shutdown();
 
+    /**
+     * Subclasses must provide an implementation that sets the window title.
+     *
+     * @param title the new title
+     */
+    public abstract void setTitle(final String title);
+
 }
index 62fae34b609e25647b27f9bce1603d98542298f1..857985562b8fcc3de75bf235c3db4871fa3d5d05 100644 (file)
@@ -160,4 +160,14 @@ public final class ECMA48Backend extends Backend {
         terminal.shutdown();
     }
 
+    /**
+     * Set the window title.
+     *
+     * @param title the new title
+     */
+    @Override
+    public void setTitle(final String title) {
+        ((ECMA48Screen) screen).setTitle(title);
+    }
+
 }
index 3dbf01778d3785d166a52b3a9cfdd7cba48e4b7f..e7f4312b0858c7f35f15356acaaee7d5a5221d87 100644 (file)
@@ -91,4 +91,14 @@ public final class SwingBackend extends Backend {
         ((SwingScreen) screen).shutdown();
     }
 
+    /**
+     * Set the window title.
+     *
+     * @param title the new title
+     */
+    @Override
+    public void setTitle(final String title) {
+        ((SwingScreen) screen).setTitle(title);
+    }
+
 }
index 522baa14b232134be70c43eb794c9eb6083b1221..1efe89f17b0db3d134f2f5918b12d2715153c49e 100644 (file)
@@ -48,6 +48,9 @@ public class Demo1 {
             if (System.getProperty("os.name").startsWith("Windows")) {
                 backendType = TApplication.BackendType.SWING;
             }
+            if (System.getProperty("os.name").startsWith("Mac")) {
+                backendType = TApplication.BackendType.SWING;
+            }
             if (System.getProperty("jexer.Swing") != null) {
                 if (System.getProperty("jexer.Swing", "false").equals("true")) {
                     backendType = TApplication.BackendType.SWING;
index 7e7126fb153f97062170d65c9983141fbc72597d..b07c783d1808b47f6088e00213f08c263978dc88 100644 (file)
@@ -74,6 +74,7 @@ public class DemoApplication extends TApplication {
         item = subMenu.addItem(2002, "&Normal (sub)");
 
         addWindowMenu();
+        addHelpMenu();
     }
 
     /**
@@ -93,6 +94,8 @@ public class DemoApplication extends TApplication {
         final OutputStream output) throws UnsupportedEncodingException {
         super(input, output);
         addAllWidgets();
+
+        getBackend().setTitle("Jexer Demo Application");
     }
 
     /**
@@ -111,6 +114,8 @@ public class DemoApplication extends TApplication {
         final PrintWriter writer, final boolean setRawMode) {
         super(input, reader, writer, setRawMode);
         addAllWidgets();
+
+        getBackend().setTitle("Jexer Demo Application");
     }
 
     /**
@@ -183,5 +188,6 @@ public class DemoApplication extends TApplication {
     public DemoApplication(final BackendType backendType) throws Exception {
         super(backendType);
         addAllWidgets();
+        getBackend().setTitle("Jexer Demo Application");
     }
 }
index a00a1806e644b31857d58feddf14fdb0a5deffb7..71878e968988fa4e9d11d132b7953c15b6f15829 100644 (file)
@@ -282,4 +282,15 @@ public final class ECMA48Screen extends Screen {
         terminal.getOutput().write(result);
         terminal.flush();
     }
+
+    /**
+     * Set the window title.
+     *
+     * @param title the new title
+     */
+    public void setTitle(final String title) {
+        terminal.getOutput().write(terminal.setTitle(title));
+        terminal.flush();
+    }
+
 }
index f448ac447f5ff89599312eb45feb110d68b6a8f8..d567a429c96bd4c60528680098e3466e1aab329b 100644 (file)
@@ -1228,6 +1228,17 @@ public final class ECMA48Terminal implements Runnable {
         return "\033[?1036l";
     }
 
+    /**
+     * Create an xterm OSC sequence to change the window title.  Note package
+     * private access.
+     *
+     * @param title the new title
+     * @return the string to emit to xterm
+     */
+    String setTitle(final String title) {
+        return "\033]2;" + title + "\007";
+    }
+
     /**
      * Create a SGR parameter sequence for a single color change.  Note
      * package private access.
index b7c8410993ce8647e23eacc8c5f8aad3545ece8f..3d0729e4feecd719b78431d93e1e54da46c9e30a 100644 (file)
@@ -165,6 +165,11 @@ public final class SwingScreen extends Screen {
          */
         SwingScreen screen;
 
+        /**
+         * If true, we were successful getting Terminus.
+         */
+        private boolean gotTerminus = false;
+
         /**
          * Width of a character cell.
          */
@@ -329,8 +334,9 @@ public final class SwingScreen extends Screen {
                         getContextClassLoader();
                 InputStream in = loader.getResourceAsStream(FONTFILE);
                 Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
-                Font terminus = terminusRoot.deriveFont(Font.PLAIN, 22);
+                Font terminus = terminusRoot.deriveFont(Font.PLAIN, 20);
                 setFont(terminus);
+                gotTerminus = true;
             } catch (Exception e) {
                 e.printStackTrace();
                 // setFont(new Font("Liberation Mono", Font.PLAIN, 24));
@@ -375,15 +381,24 @@ public final class SwingScreen extends Screen {
             Rectangle2D bounds = fm.getMaxCharBounds(gr);
             int leading = fm.getLeading();
             textWidth = (int)Math.round(bounds.getWidth());
-            textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
-            // This also produces the same number, but works better for ugly
+            // 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 (System.getProperty("os.name").startsWith("Windows")) {
                 textAdjustY = -1;
                 textAdjustX = 0;
             }
+            if (System.getProperty("os.name").startsWith("Mac")) {
+                textAdjustY = -1;
+                textAdjustX = 0;
+            }
         }
 
         /**
@@ -857,4 +872,13 @@ public final class SwingScreen extends Screen {
         return ((y - frame.top) / frame.textHeight);
     }
 
+    /**
+     * Set the window title.
+     *
+     * @param title the new title
+     */
+    public void setTitle(final String title) {
+        frame.setTitle(title);
+    }
+
 }
index b0f99ae257e2de7a5890db1eb5dc71593413b5c5..a0a978651511e2a93d04151870c4cf236c278d6e 100644 (file)
@@ -88,6 +88,15 @@ public final class TMenu extends TWindow {
     public static final int MID_WINDOW_PREVIOUS = 26;
     public static final int MID_WINDOW_CLOSE    = 27;
 
+    // Help menu
+    public static final int MID_HELP_CONTENTS           = 40;
+    public static final int MID_HELP_INDEX              = 41;
+    public static final int MID_HELP_SEARCH             = 42;
+    public static final int MID_HELP_PREVIOUS           = 43;
+    public static final int MID_HELP_HELP               = 44;
+    public static final int MID_HELP_ACTIVE_FILE        = 45;
+    public static final int MID_ABOUT                   = 46;
+
     /**
      * Public constructor.
      *
@@ -439,6 +448,31 @@ public final class TMenu extends TWindow {
             key = kbCtrlW;
             break;
 
+        case MID_HELP_CONTENTS:
+            label = "&Contents";
+            break;
+        case MID_HELP_INDEX:
+            label = "&Index";
+            key = kbShiftF1;
+            break;
+        case MID_HELP_SEARCH:
+            label = "&Topic search";
+            key = kbCtrlF1;
+            break;
+        case MID_HELP_PREVIOUS:
+            label = "&Previous topic";
+            key = kbAltF1;
+            break;
+        case MID_HELP_HELP:
+            label = "&Help on help";
+            break;
+        case MID_HELP_ACTIVE_FILE:
+            label = "Active &file...";
+            break;
+        case MID_ABOUT:
+            label = "&About...";
+            break;
+
         default:
             throw new IllegalArgumentException("Invalid menu ID: " + id);
         }
index 55a6d8d4f5858d11fb57bdf3e3e2a08deaf9e148..5b613868067b25b60db375d8a40e4febc0ef5399 100644 (file)
@@ -66,12 +66,12 @@ public final class SwingSessionInfo implements SessionInfo {
     /**
      * Text window width.
      */
-    private int windowWidth = 132;
+    private int windowWidth = 80;
 
     /**
      * Text window height.
      */
-    private int windowHeight = 40;
+    private int windowHeight = 25;
 
     /**
      * Username getter.