Bug fixes
authorKevin Lamonte <kevin.lamonte@gmail.com>
Tue, 15 Aug 2017 15:44:13 +0000 (11:44 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Tue, 15 Aug 2017 15:44:13 +0000 (11:44 -0400)
13 files changed:
README.md
docs/TODO.md
src/jexer/TApplication.java
src/jexer/TDirectoryList.java
src/jexer/TEditorWidget.java
src/jexer/TEditorWindow.java
src/jexer/TList.java
src/jexer/TScrollableWindow.java
src/jexer/TTerminalWindow.java
src/jexer/TWindow.java
src/jexer/backend/ECMA48Terminal.java
src/jexer/backend/SwingTerminal.java
src/jexer/teditor/Document.java

index 7dc0bb4b32ebe78dd9af6f88a7b5310eac880605..ff50ca75cb53c315fd3d727e4b24b02e2d855759 100644 (file)
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ Jexer currently supports three backends:
 
 Additional backends can be created by subclassing
 jexer.backend.Backend and passing it into the TApplication
-constructor.
+constructor.  See Demo5 and Demo6 for examples of other backends.
 
 The Jexer homepage, which includes additional information and binary
 release downloads, is at: https://jexer.sourceforge.io .  The Jexer
@@ -227,6 +227,11 @@ ambiguous.  This section describes such issues.
   - Closing a TTerminalWindow without exiting the process inside it
     may result in a zombie 'script' process.
 
+  - TTerminalWindow cannot notify the child process of changes in
+    window size, due to Java's lack of support for forkpty() and
+    similar.  Solving this requires C, and will be pursued only if
+    sufficient user requests come in.
+
   - Java's InputStreamReader as used by the ECMA48 backend requires a
     valid UTF-8 stream.  The default X10 encoding for mouse
     coordinates outside (160,94) can corrupt that stream, at best
index 95e1add9ad12ff36d3d174f9318484cfb497c8a6..dfb39eb71a2424efd1b4c7f20da6854383941ec5 100644 (file)
@@ -5,60 +5,83 @@ Jexer TODO List
 Roadmap
 -------
 
-BUG: TTreeView.reflow() doesn't keep the vertical dot within the
-     scrollbar.
-
 0.0.6
 
+- TSpinner
+- TComboBox
+- TCalendar
+
 - TEditor
+  - Horizontal scrollbar integration
   - True tokenization and syntax highlighting: Java, C, Clojure, XML
+  - Carat notation for control characters
   - Tab character support
-  - Cut and Paste
+  - Cut/copy/paste
+  - Performance: behave smoothly on 100MB text files
+
+0.0.7
 
 - Finish up multiscreen support:
   - cmAbort to cmScreenDisconnected
   - cmScreenConnected
   - Handle screen resizes
 
-- TSpinner
-- TComboBox
-- TCalendar
+- TEditor
+  - Word wrap
+  - Performance: behave smoothly on 1GB text files
 
-0.0.7
+- Additional main color themes:
+  - Dark / L33t
+  - Green / NoReallyElite
+  - Red/brown
+  - Monochrome
+  - OMGPonies
+
+0.0.8
 
 - THelpWindow
-  - TText + clickable links
+  - TEditor + clickable links
   - Index
 
-0.0.8
+- TEditor
+  - Expose API:
+    - Cursor movement
+    - Movement within document
+    - Cut/copy/paste
 
-- Undo / Redo support
+0.0.9
+
+- TEditor:
+  - Undo / Redo support
 
 0.1.0: BETA RELEASE and BUG HUNT
 
 - Verify vttest in multiple tterminals.
 
+0.2.0:
+
+- Drag and drop
+  - TEditor
+  - TField
+  - TText
+  - TTerminal
+  - TComboBox
+
 1.0.0
 
-- Maven artifact.
+- Publish to the whole wide world
 
 
 1.1.0 Wishlist
 --------------
 
 - TTerminal
-  - Handle resize events (pass to child process)
+  - Handle resize events (pass to child process).  Will need to switch
+    to forkpty(), or ship a C wrapper process.
 
 - Screen
   - Allow complex characters in putCharXY() and detect them in putStringXY().
 
-- Drag and drop
-  - TEditor
-  - TField
-  - TText
-  - TTerminal
-  - TComboBox
-
 
 
 Regression Checklist
index 691e1c7295f087e700f469c86bab059aa256cb41..c0166b11c3daf492e7e5afc04ff0af4a0a2ca5a6 100644 (file)
@@ -1396,6 +1396,13 @@ public class TApplication implements Runnable {
             return;
         }
 
+        // Whatever window might be moving/dragging, stop it now.
+        for (TWindow w: windows) {
+            if (w.inMovements()) {
+                w.stopMovements();
+            }
+        }
+
         assert (windows.size() > 0);
 
         if (window.isHidden()) {
@@ -1455,6 +1462,13 @@ public class TApplication implements Runnable {
             return;
         }
 
+        // Whatever window might be moving/dragging, stop it now.
+        for (TWindow w: windows) {
+            if (w.inMovements()) {
+                w.stopMovements();
+            }
+        }
+
         assert (windows.size() > 0);
 
         if (!window.hidden) {
@@ -1486,6 +1500,13 @@ public class TApplication implements Runnable {
             return;
         }
 
+        // Whatever window might be moving/dragging, stop it now.
+        for (TWindow w: windows) {
+            if (w.inMovements()) {
+                w.stopMovements();
+            }
+        }
+
         assert (windows.size() > 0);
 
         if (window.hidden) {
@@ -1511,6 +1532,13 @@ public class TApplication implements Runnable {
         }
 
         synchronized (windows) {
+            // Whatever window might be moving/dragging, stop it now.
+            for (TWindow w: windows) {
+                if (w.inMovements()) {
+                    w.stopMovements();
+                }
+            }
+
             int z = window.getZ();
             window.setZ(-1);
             window.onUnfocus();
@@ -1575,6 +1603,12 @@ public class TApplication implements Runnable {
         assert (activeWindow != null);
 
         synchronized (windows) {
+            // Whatever window might be moving/dragging, stop it now.
+            for (TWindow w: windows) {
+                if (w.inMovements()) {
+                    w.stopMovements();
+                }
+            }
 
             // Swap z/active between active window and the next in the list
             int activeWindowI = -1;
@@ -1633,6 +1667,13 @@ public class TApplication implements Runnable {
         }
 
         synchronized (windows) {
+            // Whatever window might be moving/dragging, stop it now.
+            for (TWindow w: windows) {
+                if (w.inMovements()) {
+                    w.stopMovements();
+                }
+            }
+
             // Do not allow a modal window to spawn a non-modal window.  If a
             // modal window is active, then this window will become modal
             // too.
index 642366b7e23c42f11bf1baedacb8363c15fa1e00..4b62bf316b46856ed4d634b834361287b8b7662c 100644 (file)
@@ -73,6 +73,11 @@ public final class TDirectoryList extends TList {
             }
         }
         setList(newStrings);
+
+        // Select the first entry
+        if (getMaxSelectedIndex() >= 0) {
+            setSelectedIndex(0);
+        }
     }
 
     /**
index 8d2a0104b4c598aec5de23f06a4e879e4871171a..690c5731f280a936cd1ce6f5ac3bbcf0c4d4f4ac 100644 (file)
@@ -183,8 +183,13 @@ public final class TEditorWidget extends TWidget {
             // Need to move topLine to bring document back into view.
             if (topLineIsTop) {
                 topLine = line - (getHeight() - 1);
+                if (topLine < 0) {
+                    topLine = 0;
+                }
+                assert (topLine >= 0);
             } else {
                 topLine = line;
+                assert (topLine >= 0);
             }
         }
 
@@ -193,6 +198,7 @@ public final class TEditorWidget extends TWidget {
         */
 
         // Document is in view, let's set cursorY
+        assert (line >= topLine);
         setCursorY(line - topLine);
         alignCursor();
     }
@@ -385,7 +391,11 @@ public final class TEditorWidget extends TWidget {
      * @param row the new editing row number.  Row 1 is the first row.
      */
     public void setEditingRowNumber(final int row) {
-        document.setLineNumber(row - 1);
+        assert (row > 0);
+        if ((row > 0) && (row < document.getLineCount())) {
+            document.setLineNumber(row - 1);
+            alignTopLine(true);
+        }
     }
 
     /**
@@ -404,7 +414,10 @@ public final class TEditorWidget extends TWidget {
      * column.
      */
     public void setEditingColumnNumber(final int column) {
-        document.setCursor(column - 1);
+        if ((column > 0) && (column < document.getLineLength())) {
+            document.setCursor(column - 1);
+            alignCursor();
+        }
     }
 
     /**
index 69e254b9df70f2b4303edc9a9806b64bacd90eb1..5bf664d254e515f95d41418ef383ae01559dcdb4 100644 (file)
@@ -41,6 +41,7 @@ import jexer.TWidget;
 import jexer.bits.CellAttributes;
 import jexer.bits.GraphicsChars;
 import jexer.event.TCommandEvent;
+import jexer.event.TKeypressEvent;
 import jexer.event.TMouseEvent;
 import jexer.event.TResizeEvent;
 import static jexer.TCommand.*;
@@ -201,7 +202,7 @@ public class TEditorWindow extends TScrollableWindow {
      * editor.
      *
      * @param mouse a mouse-based event
-     * @return whether or not the mouse is on the emulator
+     * @return whether or not the mouse is on the editor
      */
     private final boolean mouseOnEditor(final TMouseEvent mouse) {
         if ((mouse.getAbsoluteX() >= getAbsoluteX() + 1)
@@ -235,8 +236,69 @@ public class TEditorWindow extends TScrollableWindow {
                 // Vertical scrollbar actions
                 editField.setEditingRowNumber(getVerticalValue());
             }
+        }
+    }
+
+    /**
+     * Handle mouse release events.
+     *
+     * @param mouse mouse button release event
+     */
+    @Override
+    public void onMouseUp(final TMouseEvent mouse) {
+        // Use TWidget's code to pass the event to the children.
+        super.onMouseUp(mouse);
+
+        if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
+            // Clicked on vertical scrollbar
+            editField.setEditingRowNumber(getVerticalValue());
+        }
+
+        // TODO: horizontal scrolling
+    }
+
+    /**
+     * Method that subclasses can override to handle mouse movements.
+     *
+     * @param mouse mouse motion event
+     */
+    @Override
+    public void onMouseMotion(final TMouseEvent mouse) {
+        // Use TWidget's code to pass the event to the children.
+        super.onMouseMotion(mouse);
+
+        if (mouseOnEditor(mouse) && mouse.isMouse1()) {
+            // The editor might have changed, update the scollbars.
+            setBottomValue(editField.getMaximumRowNumber());
+            setVerticalValue(editField.getEditingRowNumber());
+            setRightValue(editField.getMaximumColumnNumber());
+            setHorizontalValue(editField.getEditingColumnNumber());
+        } else {
+            if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
+                // Clicked/dragged on vertical scrollbar
+                editField.setEditingRowNumber(getVerticalValue());
+            }
+
             // TODO: horizontal scrolling
         }
+
+    }
+
+    /**
+     * Handle keystrokes.
+     *
+     * @param keypress keystroke event
+     */
+    @Override
+    public void onKeypress(final TKeypressEvent keypress) {
+        // Use TWidget's code to pass the event to the children.
+        super.onKeypress(keypress);
+
+        // The editor might have changed, update the scollbars.
+        setBottomValue(editField.getMaximumRowNumber());
+        setVerticalValue(editField.getEditingRowNumber());
+        setRightValue(editField.getMaximumColumnNumber());
+        setHorizontalValue(editField.getEditingColumnNumber());
     }
 
     /**
index 71600485fe2b9cb1646a8df8a3c0fa5c7af5755d..644c5647afafa5cb7ce81d3f3a75b3faa3908dd1 100644 (file)
@@ -81,6 +81,15 @@ public class TList extends TScrollableWidget {
         return null;
     }
 
+    /**
+     * Get the maximum selection index value.
+     *
+     * @return -1 if the list is empty
+     */
+    public final int getMaxSelectedIndex() {
+        return strings.size() - 1;
+    }
+
     /**
      * Set the new list of strings to display.
      *
index ce20df7df9f5d1f9d68cbccaa38345277feea2e7..ae5f50c570e645a78f79d4170a73138e0fe3d41a 100644 (file)
@@ -28,6 +28,7 @@
  */
 package jexer;
 
+import jexer.event.TMouseEvent;
 import jexer.event.TResizeEvent;
 
 /**
@@ -599,4 +600,46 @@ public class TScrollableWindow extends TWindow implements Scrollable {
         }
     }
 
+    /**
+     * Check if a mouse press/release/motion event coordinate is over the
+     * vertical scrollbar.
+     *
+     * @param mouse a mouse-based event
+     * @return whether or not the mouse is on the scrollbar
+     */
+    protected final boolean mouseOnVerticalScroller(final TMouseEvent mouse) {
+        if (vScroller == null) {
+            return false;
+        }
+        if ((mouse.getAbsoluteX() == vScroller.getAbsoluteX())
+            && (mouse.getAbsoluteY() >= vScroller.getAbsoluteY())
+            && (mouse.getAbsoluteY() <  vScroller.getAbsoluteY() +
+                vScroller.getHeight())
+        ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if a mouse press/release/motion event coordinate is over the
+     * horizontal scrollbar.
+     *
+     * @param mouse a mouse-based event
+     * @return whether or not the mouse is on the scrollbar
+     */
+    protected final boolean mouseOnHorizontalScroller(final TMouseEvent mouse) {
+        if (hScroller == null) {
+            return false;
+        }
+        if ((mouse.getAbsoluteY() == hScroller.getAbsoluteY())
+            && (mouse.getAbsoluteX() >= hScroller.getAbsoluteX())
+            && (mouse.getAbsoluteX() <  hScroller.getAbsoluteX() +
+                hScroller.getWidth())
+        ) {
+            return true;
+        }
+        return false;
+    }
+
 }
index 17f32e0772db527b54fadc59378263a4e517f340..96043e85af96849dc2ceaea5b09d83b1387e0c0d 100644 (file)
  */
 package jexer;
 
-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;
@@ -189,7 +186,7 @@ public class TTerminalWindow extends TScrollableWindow {
             emulator = new ECMA48(deviceType, shell.getInputStream(),
                 shell.getOutputStream());
         } catch (IOException e) {
-            e.printStackTrace();
+            messageBox("Error", "Error launching shell: " + e.getMessage());
         }
 
         // Setup the scroll bars
@@ -236,39 +233,6 @@ public class TTerminalWindow extends TScrollableWindow {
         }
     }
 
-    /**
-     * Public constructor.
-     *
-     * @param application TApplication that manages this window
-     * @param x column relative to parent
-     * @param y row relative to parent
-     * @param flags mask of CENTERED, MODAL, or RESIZABLE
-     * @param input an InputStream connected to the remote side.  For type ==
-     * XTERM, input is converted to a Reader with UTF-8 encoding.
-     * @param output an OutputStream connected to the remote user.  For type
-     * == XTERM, output is converted to a Writer with UTF-8 encoding.
-     * @throws UnsupportedEncodingException if an exception is thrown when
-     * creating the InputStreamReader
-     */
-    public TTerminalWindow(final TApplication application, final int x,
-        final int y, final int flags, final InputStream input,
-        final OutputStream output) throws UnsupportedEncodingException {
-
-        super(application, "Terminal", x, y, 80 + 2, 24 + 2, flags);
-
-        emulator = new ECMA48(ECMA48.DeviceType.XTERM, input, output);
-
-        // Setup the scroll bars
-        onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
-                getHeight()));
-
-        // Claim the keystrokes the emulator will need.
-        addShortcutKeys();
-
-        // Add shortcut text
-        newStatusBar("Terminal session executing...");
-    }
-
     /**
      * Draw the display buffer.
      */
index 406eb7897a21a152329dbfc9c6ddee3bd52251aa..cde0113a009b01a26c397b4badf814cb7281532f 100644 (file)
@@ -547,6 +547,27 @@ public class TWindow extends TWidget {
     // General behavior -------------------------------------------------------
     // ------------------------------------------------------------------------
 
+    /**
+     * See if this window is undergoing any movement/resize/etc.
+     *
+     * @return true if the window is moving
+     */
+    public boolean inMovements() {
+        if (inWindowResize || inWindowMove || inKeyboardResize) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Stop any pending movement/resize/etc.
+     */
+    public void stopMovements() {
+        inWindowResize = false;
+        inWindowMove = false;
+        inKeyboardResize = false;
+    }
+
     /**
      * Returns true if this window is modal.
      *
index 78aae45c63b315d2c40a340f60071dbdd527f0be..f23f6f447864a927e4dc172ee7ec6274834b4c69 100644 (file)
@@ -746,6 +746,8 @@ public final class ECMA48Terminal extends LogicalScreen
     public void flushPhysical() {
         String result = flushString();
         if ((cursorVisible)
+            && (cursorY >= 0)
+            && (cursorX >= 0)
             && (cursorY <= height - 1)
             && (cursorX <= width - 1)
         ) {
index b7a16249d49dda32af22bf5f053c8c02e6f99a3e..8ac6c2b54afaa708f28616c29c0110018ebcdf8e 100644 (file)
@@ -633,6 +633,8 @@ public final class SwingTerminal extends LogicalScreen
     private void drawCursor(final Graphics gr) {
 
         if (cursorVisible
+            && (cursorY >= 0)
+            && (cursorX >= 0)
             && (cursorY <= height - 1)
             && (cursorX <= width - 1)
             && cursorBlinkVisible
@@ -942,6 +944,8 @@ public final class SwingTerminal extends LogicalScreen
         }
 
         if (cursorVisible
+            && (cursorY >= 0)
+            && (cursorX >= 0)
             && (cursorY <= height - 1)
             && (cursorX <= width - 1)
         ) {
index c8ab746096f20971871fe8da99efbe67dd4fcade..c84c20787e6453e987db5fcec7f01911b356e959 100644 (file)
@@ -456,4 +456,13 @@ public class Document {
         return n;
     }
 
+    /**
+     * Get the current line length.
+     *
+     * @return the number of cells needed to display the current line
+     */
+    public int getLineLength() {
+        return lines.get(lineNumber).getDisplayLength();
+    }
+
 }