TEditor 80% complete
authorKevin Lamonte <kevin.lamonte@gmail.com>
Mon, 14 Aug 2017 18:45:33 +0000 (14:45 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Mon, 14 Aug 2017 18:45:33 +0000 (14:45 -0400)
14 files changed:
docs/TODO.md
src/jexer/TApplication.java
src/jexer/TCommand.java
src/jexer/TEditorWidget.java
src/jexer/TEditorWindow.java [new file with mode: 0644]
src/jexer/TScrollableWindow.java
src/jexer/TWindow.java
src/jexer/bits/GraphicsChars.java
src/jexer/demos/DemoApplication.java
src/jexer/demos/DemoEditorWindow.java
src/jexer/demos/DemoMainWindow.java
src/jexer/teditor/Document.java
src/jexer/teditor/Line.java
src/jexer/teditor/Word.java

index e59638221e8828c0e424e6127c9678a6f60b5fd3..20ce1fe6a49b4ea5c0e9710648e2be03ed949606 100644 (file)
@@ -11,17 +11,18 @@ BUG: TTreeView.reflow() doesn't keep the vertical dot within the
 0.0.5
 
 - TEditor
+  - Document
+    - Filename
+    - Pick appropriate Highlighter: plain, Java, XML, ...
   - TEditorWidget:
-    - Mouse wheel is buggy as hell
-    - Actual editing
     - Cut and Paste
-  - TEditorWindow extends TScrollableWindow
   - TTextArea extends TScrollableWidget
 
 0.0.6
 
 - TEditor
-  - True tokenization and syntax highlighting: Java, C, Clojure
+  - True tokenization and syntax highlighting: Java, C, Clojure, XML
+  - Tab character support
 
 - Finish up multiscreen support:
   - cmAbort to cmScreenDisconnected
index 8b436ab9a99ec9219b9755ffce379c62d834e10f..691e1c7295f087e700f469c86bab059aa256cb41 100644 (file)
@@ -2420,6 +2420,16 @@ public class TApplication implements Runnable {
             return true;
         }
 
+        if (command.equals(cmMenu)) {
+            if (!modalWindowActive() && (activeMenu == null)) {
+                if (menus.size() > 0) {
+                    menus.get(0).setActive(true);
+                    activeMenu = menus.get(0);
+                    return true;
+                }
+            }
+        }
+
         return false;
     }
 
index 8381d9e3d8bdc8663cf934cf4d1ef7a72c370089..a814fae7c15d79ee2f201d547be83e32e95a3382 100644 (file)
@@ -121,6 +121,16 @@ public class TCommand {
      */
     public static final int HELP                = 20;
 
+    /**
+     * Enter first menu.
+     */
+    public static final int MENU                = 21;
+
+    /**
+     * Save file.
+     */
+    public static final int SAVE                = 30;
+
     /**
      * Type of command, one of EXIT, CASCADE, etc.
      */
@@ -189,5 +199,7 @@ public class TCommand {
     public static final TCommand cmWindowPrevious = new TCommand(WINDOW_PREVIOUS);
     public static final TCommand cmWindowClose  = new TCommand(WINDOW_CLOSE);
     public static final TCommand cmHelp         = new TCommand(HELP);
+    public static final TCommand cmSave         = new TCommand(SAVE);
+    public static final TCommand cmMenu         = new TCommand(MENU);
 
 }
index 361ed83b9be4bc8355c53f9ad4318746e78363b8..cf4d887fd3cba5525637f406e4b776e517950d54 100644 (file)
@@ -28,6 +28,8 @@
  */
 package jexer;
 
+import java.io.IOException;
+
 import jexer.bits.CellAttributes;
 import jexer.event.TKeypressEvent;
 import jexer.event.TMouseEvent;
@@ -121,34 +123,16 @@ public final class TEditorWidget extends TWidget {
     @Override
     public void onMouseDown(final TMouseEvent mouse) {
         if (mouse.isMouseWheelUp()) {
-            if (getCursorY() == getHeight() - 1) {
-                if (document.up()) {
-                    if (topLine > 0) {
-                        topLine--;
-                    }
-                    alignCursor();
-                }
-            } else {
-                if (topLine > 0) {
-                    topLine--;
-                    setCursorY(getCursorY() + 1);
-                }
+            if (topLine > 0) {
+                topLine--;
+                alignDocument(false);
             }
             return;
         }
         if (mouse.isMouseWheelDown()) {
-            if (getCursorY() == 0) {
-                if (document.down()) {
-                    if (topLine < document.getLineNumber()) {
-                        topLine++;
-                    }
-                    alignCursor();
-                }
-            } else {
-                if (topLine < document.getLineCount() - getHeight()) {
-                    topLine++;
-                    setCursorY(getCursorY() - 1);
-                }
+            if (topLine < document.getLineCount() - 1) {
+                topLine++;
+                alignDocument(true);
             }
             return;
         }
@@ -185,6 +169,63 @@ public final class TEditorWidget extends TWidget {
         super.onMouseDown(mouse);
     }
 
+    /**
+     * Align visible area with document current line.
+     *
+     * @param topLineIsTop if true, make the top visible line the document
+     * current line if it was off-screen.  If false, make the bottom visible
+     * line the document current line.
+     */
+    private void alignTopLine(final boolean topLineIsTop) {
+        int line = document.getLineNumber();
+
+        if ((line < topLine) || (line > topLine + getHeight() - 1)) {
+            // Need to move topLine to bring document back into view.
+            if (topLineIsTop) {
+                topLine = line - (getHeight() - 1);
+            } else {
+                topLine = line;
+            }
+        }
+
+        /*
+        System.err.println("line " + line + " topLine " + topLine);
+        */
+
+        // Document is in view, let's set cursorY
+        setCursorY(line - topLine);
+        alignCursor();
+    }
+
+    /**
+     * Align document current line with visible area.
+     *
+     * @param topLineIsTop if true, make the top visible line the document
+     * current line if it was off-screen.  If false, make the bottom visible
+     * line the document current line.
+     */
+    private void alignDocument(final boolean topLineIsTop) {
+        int line = document.getLineNumber();
+
+        if ((line < topLine) || (line > topLine + getHeight() - 1)) {
+            // Need to move document to ensure it fits view.
+            if (topLineIsTop) {
+                document.setLineNumber(topLine);
+            } else {
+                document.setLineNumber(topLine + (getHeight() - 1));
+            }
+        }
+
+        /*
+        System.err.println("getLineNumber() " + document.getLineNumber() +
+            " topLine " + topLine);
+        */
+
+        // Document is in view, let's set cursorY
+        setCursorY(document.getLineNumber() - topLine);
+        alignCursor();
+    }
+
     /**
      * Align visible cursor with document cursor.
      */
@@ -224,57 +265,17 @@ public final class TEditorWidget extends TWidget {
                 alignCursor();
             }
         } else if (keypress.equals(kbUp)) {
-            if (document.up()) {
-                if (getCursorY() > 0) {
-                    setCursorY(getCursorY() - 1);
-                } else {
-                    if (topLine > 0) {
-                        topLine--;
-                    }
-                }
-                alignCursor();
-            }
+            document.up();
+            alignTopLine(false);
         } else if (keypress.equals(kbDown)) {
-            if (document.down()) {
-                if (getCursorY() < getHeight() - 1) {
-                    setCursorY(getCursorY() + 1);
-                } else {
-                    if (topLine < document.getLineCount() - getHeight()) {
-                        topLine++;
-                    }
-                }
-                alignCursor();
-            }
+            document.down();
+            alignTopLine(true);
         } else if (keypress.equals(kbPgUp)) {
-            for (int i = 0; i < getHeight() - 1; i++) {
-                if (document.up()) {
-                    if (getCursorY() > 0) {
-                        setCursorY(getCursorY() - 1);
-                    } else {
-                        if (topLine > 0) {
-                            topLine--;
-                        }
-                    }
-                    alignCursor();
-                } else {
-                    break;
-                }
-            }
+            document.up(getHeight() - 1);
+            alignTopLine(false);
         } else if (keypress.equals(kbPgDn)) {
-            for (int i = 0; i < getHeight() - 1; i++) {
-                if (document.down()) {
-                    if (getCursorY() < getHeight() - 1) {
-                        setCursorY(getCursorY() + 1);
-                    } else {
-                        if (topLine < document.getLineCount() - getHeight()) {
-                            topLine++;
-                        }
-                    }
-                    alignCursor();
-                } else {
-                    break;
-                }
-            }
+            document.down(getHeight() - 1);
+            alignTopLine(true);
         } else if (keypress.equals(kbHome)) {
             if (document.home()) {
                 leftColumn = 0;
@@ -297,29 +298,25 @@ public final class TEditorWidget extends TWidget {
         } else if (keypress.equals(kbCtrlEnd)) {
             document.setLineNumber(document.getLineCount() - 1);
             document.end();
-            topLine = document.getLineCount() - getHeight();
-            if (topLine < 0) {
-                topLine = 0;
-            }
-            if (document.getLineCount() > getHeight()) {
-                setCursorY(getHeight() - 1);
-            } else {
-                setCursorY(document.getLineCount() - 1);
-            }
-            alignCursor();
+            alignTopLine(false);
         } else if (keypress.equals(kbIns)) {
             document.setOverwrite(!document.getOverwrite());
         } else if (keypress.equals(kbDel)) {
+            // TODO: join lines
             document.del();
+            alignCursor();
         } else if (keypress.equals(kbBackspace)) {
             document.backspace();
             alignCursor();
+        } else if (keypress.equals(kbEnter)) {
+            // TODO: split lines
         } else if (!keypress.getKey().isFnKey()
             && !keypress.getKey().isAlt()
             && !keypress.getKey().isCtrl()
         ) {
             // Plain old keystroke, process it
             document.addChar(keypress.getKey().getChar());
+            alignCursor();
         } else {
             // Pass other keys (tab etc.) on to TWidget
             super.onKeypress(keypress);
@@ -354,4 +351,87 @@ public final class TEditorWidget extends TWidget {
         }
     }
 
+    /**
+     * Get the number of lines in the underlying Document.
+     *
+     * @return the number of lines
+     */
+    public int getLineCount() {
+        return document.getLineCount();
+    }
+
+    /**
+     * Get the current editing row number.  1-based.
+     *
+     * @return the editing row number.  Row 1 is the first row.
+     */
+    public int getEditingRowNumber() {
+        return document.getLineNumber() + 1;
+    }
+
+    /**
+     * Set the current editing row number.  1-based.
+     *
+     * @param row the new editing row number.  Row 1 is the first row.
+     */
+    public void setEditingRowNumber(final int row) {
+        document.setLineNumber(row - 1);
+    }
+
+    /**
+     * Get the current editing column number.  1-based.
+     *
+     * @return the editing column number.  Column 1 is the first column.
+     */
+    public int getEditingColumnNumber() {
+        return document.getCursor() + 1;
+    }
+
+    /**
+     * Set the current editing column number.  1-based.
+     *
+     * @param column the new editing column number.  Column 1 is the first
+     * column.
+     */
+    public void setEditingColumnNumber(final int column) {
+        document.setCursor(column - 1);
+    }
+
+    /**
+     * Get the maximum possible row number.  1-based.
+     *
+     * @return the maximum row number.  Row 1 is the first row.
+     */
+    public int getMaximumRowNumber() {
+        return document.getLineCount() + 1;
+    }
+
+    /**
+     * Get the maximum possible column number.  1-based.
+     *
+     * @return the maximum column number.  Column 1 is the first column.
+     */
+    public int getMaximumColumnNumber() {
+        return document.getLineLengthMax() + 1;
+    }
+
+    /**
+     * Get the dirty value.
+     *
+     * @return true if the buffer is dirty
+     */
+    public boolean isDirty() {
+        return document.isDirty();
+    }
+
+    /**
+     * Save contents to file.
+     *
+     * @param filename file to save to
+     * @throws IOException if a java.io operation throws
+     */
+    public void saveToFilename(final String filename) throws IOException {
+        document.saveToFilename(filename);
+    }
+
 }
diff --git a/src/jexer/TEditorWindow.java b/src/jexer/TEditorWindow.java
new file mode 100644 (file)
index 0000000..f96b177
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * 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:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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
+ */
+package jexer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Scanner;
+
+import jexer.TApplication;
+import jexer.TEditorWidget;
+import jexer.THScroller;
+import jexer.TScrollableWindow;
+import jexer.TVScroller;
+import jexer.TWidget;
+import jexer.bits.CellAttributes;
+import jexer.bits.GraphicsChars;
+import jexer.event.TCommandEvent;
+import jexer.event.TMouseEvent;
+import jexer.event.TResizeEvent;
+import static jexer.TCommand.*;
+import static jexer.TKeypress.*;
+
+/**
+ * TEditorWindow is a basic text file editor.
+ */
+public class TEditorWindow extends TScrollableWindow {
+
+    /**
+     * Hang onto my TEditor so I can resize it with the window.
+     */
+    private TEditorWidget editField;
+
+    /**
+     * The fully-qualified name of the file being edited.
+     */
+    private String filename = "";
+
+    /**
+     * Setup other fields after the editor is created.
+     */
+    private void setupAfterEditor() {
+        hScroller = new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
+        vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
+        setMinimumWindowWidth(25);
+        setMinimumWindowHeight(10);
+        setTopValue(1);
+        setBottomValue(editField.getMaximumRowNumber());
+        setLeftValue(1);
+        setRightValue(editField.getMaximumColumnNumber());
+
+        statusBar = newStatusBar("Editor");
+        statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
+        statusBar.addShortcutKeypress(kbF2, cmSave, "Save");
+        statusBar.addShortcutKeypress(kbF3, cmOpen, "Open");
+        statusBar.addShortcutKeypress(kbF10, cmMenu, "Menu");
+    }
+
+    /**
+     * Public constructor sets window title.
+     *
+     * @param parent the main application
+     * @param title the window title
+     */
+    public TEditorWindow(final TApplication parent, final String title) {
+
+        super(parent, title, 0, 0, parent.getScreen().getWidth(),
+            parent.getScreen().getHeight() - 2, RESIZABLE);
+
+        editField = addEditor("", 0, 0, getWidth() - 2, getHeight() - 2);
+        setupAfterEditor();
+    }
+
+    /**
+     * Public constructor sets window title and contents.
+     *
+     * @param parent the main application
+     * @param title the window title, usually a filename
+     * @param contents the data for the editing window, usually the file data
+     */
+    public TEditorWindow(final TApplication parent, final String title,
+        final String contents) {
+
+        super(parent, title, 0, 0, parent.getScreen().getWidth(),
+            parent.getScreen().getHeight() - 2, RESIZABLE);
+
+        filename = title;
+        editField = addEditor(contents, 0, 0, getWidth() - 2, getHeight() - 2);
+        setupAfterEditor();
+    }
+
+    /**
+     * Public constructor.
+     *
+     * @param parent the main application
+     */
+    public TEditorWindow(final TApplication parent) {
+        this(parent, "New Text Document");
+    }
+
+    /**
+     * Draw the window.
+     */
+    @Override
+    public void draw() {
+        // Draw as normal.
+        super.draw();
+
+        // Add the row:col on the bottom row
+        CellAttributes borderColor = getBorder();
+        String location = String.format(" %d:%d ",
+            editField.getEditingRowNumber(),
+            editField.getEditingColumnNumber());
+        int colon = location.indexOf(':');
+        putStringXY(10 - colon, getHeight() - 1, location, borderColor);
+
+        if (editField.isDirty()) {
+            putCharXY(2, getHeight() - 1, GraphicsChars.OCTOSTAR, borderColor);
+        }
+    }
+
+    /**
+     * Check if a mouse press/release/motion event coordinate is over the
+     * editor.
+     *
+     * @param mouse a mouse-based event
+     * @return whether or not the mouse is on the emulator
+     */
+    private final boolean mouseOnEditor(final TMouseEvent mouse) {
+        if ((mouse.getAbsoluteX() >= getAbsoluteX() + 1)
+            && (mouse.getAbsoluteX() <  getAbsoluteX() + getWidth() - 1)
+            && (mouse.getAbsoluteY() >= getAbsoluteY() + 1)
+            && (mouse.getAbsoluteY() <  getAbsoluteY() + getHeight() - 1)
+        ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle mouse press events.
+     *
+     * @param mouse mouse button press event
+     */
+    @Override
+    public void onMouseDown(final TMouseEvent mouse) {
+        if (mouseOnEditor(mouse)) {
+            editField.onMouseDown(mouse);
+            setBottomValue(editField.getMaximumRowNumber());
+            setVerticalValue(editField.getEditingRowNumber());
+            setRightValue(editField.getMaximumColumnNumber());
+            setHorizontalValue(editField.getEditingColumnNumber());
+        } else {
+            // Let the scrollbars get the event
+            super.onMouseDown(mouse);
+
+            if (mouse.isMouseWheelUp() || mouse.isMouseWheelDown()) {
+                editField.setEditingRowNumber(getVerticalValue());
+            }
+            // TODO: horizontal scrolling
+        }
+    }
+
+    /**
+     * Handle window/screen resize events.
+     *
+     * @param event resize event
+     */
+    @Override
+    public void onResize(final TResizeEvent event) {
+        if (event.getType() == TResizeEvent.Type.WIDGET) {
+            // Resize the text field
+            TResizeEvent editSize = new TResizeEvent(TResizeEvent.Type.WIDGET,
+                event.getWidth() - 2, event.getHeight() - 2);
+            editField.onResize(editSize);
+
+            // Have TScrollableWindow handle the scrollbars
+            super.onResize(event);
+            return;
+        }
+
+        // Pass to children instead
+        for (TWidget widget: getChildren()) {
+            widget.onResize(event);
+        }
+    }
+
+    /**
+     * Method that subclasses can override to handle posted command events.
+     *
+     * @param command command event
+     */
+    @Override
+    public void onCommand(final TCommandEvent command) {
+        if (command.equals(cmOpen)) {
+            try {
+                String filename = fileOpenBox(".");
+                 if (filename != null) {
+                     try {
+                         File file = new File(filename);
+                         StringBuilder fileContents = new StringBuilder();
+                         Scanner scanner = new Scanner(file);
+                         String EOL = System.getProperty("line.separator");
+
+                         try {
+                             while (scanner.hasNextLine()) {
+                                 fileContents.append(scanner.nextLine() + EOL);
+                             }
+                             new TEditorWindow(getApplication(), filename,
+                                 fileContents.toString());
+                         } finally {
+                             scanner.close();
+                         }
+                     } catch (IOException e) {
+                         // TODO: make this a message box
+                         e.printStackTrace();
+                     }
+                 }
+            } catch (IOException e) {
+                // TODO: make this a message box
+                e.printStackTrace();
+            }
+            return;
+        }
+
+        if (command.equals(cmSave)) {
+            if (filename.length() > 0) {
+                try {
+                    editField.saveToFilename(filename);
+                } catch (IOException e) {
+                    // TODO: make this a message box
+                    e.printStackTrace();
+                }
+            }
+            return;
+        }
+
+        // Didn't handle it, let children get it instead
+        super.onCommand(command);
+    }
+
+}
index 8935ac349298ebed6fb4840467dcdec802e4b217..ce20df7df9f5d1f9d68cbccaa38345277feea2e7 100644 (file)
@@ -53,8 +53,8 @@ public class TScrollableWindow extends TWindow implements Scrollable {
     protected void placeScrollbars() {
         if (hScroller != null) {
             hScroller.setY(getHeight() - 2);
-            hScroller.setWidth(getWidth() - 3);
-            hScroller.setBigChange(getWidth() - 3);
+            hScroller.setWidth(getWidth() - hScroller.getX() - 3);
+            hScroller.setBigChange(getWidth() - hScroller.getX() - 3);
         }
         if (vScroller != null) {
             vScroller.setX(getWidth() - 2);
index 32907a8a9c9046ec4329182623b0137c72391086..962130560af8c7ed9cd3f9367db389c7b7bbc9a4 100644 (file)
@@ -284,9 +284,60 @@ public class TWindow extends TWidget {
      * @param maximumWindowWidth new maximum width
      */
     public final void setMaximumWindowWidth(final int maximumWindowWidth) {
+        if ((maximumWindowWidth != -1)
+            && (maximumWindowWidth < minimumWindowWidth + 1)
+        ) {
+            throw new IllegalArgumentException("Maximum window width cannot " +
+                "be smaller than minimum window width + 1");
+        }
         this.maximumWindowWidth = maximumWindowWidth;
     }
 
+    /**
+     * Set the minimum width for this window.
+     *
+     * @param minimumWindowWidth new minimum width
+     */
+    public final void setMinimumWindowWidth(final int minimumWindowWidth) {
+        if ((maximumWindowWidth != -1)
+            && (minimumWindowWidth > maximumWindowWidth - 1)
+        ) {
+            throw new IllegalArgumentException("Minimum window width cannot " +
+                "be larger than maximum window width - 1");
+        }
+        this.minimumWindowWidth = minimumWindowWidth;
+    }
+
+    /**
+     * Set the maximum height for this window.
+     *
+     * @param maximumWindowHeight new maximum height
+     */
+    public final void setMaximumWindowHeight(final int maximumWindowHeight) {
+        if ((maximumWindowHeight != -1)
+            && (maximumWindowHeight < minimumWindowHeight + 1)
+        ) {
+            throw new IllegalArgumentException("Maximum window height cannot " +
+                "be smaller than minimum window height + 1");
+        }
+        this.maximumWindowHeight = maximumWindowHeight;
+    }
+
+    /**
+     * Set the minimum height for this window.
+     *
+     * @param minimumWindowHeight new minimum height
+     */
+    public final void setMinimumWindowHeight(final int minimumWindowHeight) {
+        if ((maximumWindowHeight != -1)
+            && (minimumWindowHeight > maximumWindowHeight - 1)
+        ) {
+            throw new IllegalArgumentException("Minimum window height cannot " +
+                "be larger than maximum window height - 1");
+        }
+        this.minimumWindowHeight = minimumWindowHeight;
+    }
+
     /**
      * Recenter the window on-screen.
      */
index 86265828bf2ae38e75b6130f7fae4a85bceb7537..32403090a4301a0ce800b1dfe1fe3668cd335b20 100644 (file)
@@ -146,4 +146,5 @@ public final class GraphicsChars {
     public static final char WINDOW_LEFT_BOTTOM_DOUBLE  = CP437[0xC8];
     public static final char WINDOW_RIGHT_BOTTOM_DOUBLE = CP437[0xBC];
     public static final char VERTICAL_BAR               = CP437[0xB3];
+    public static final char OCTOSTAR                   = CP437[0x0F];
 }
index 43bb709020c45a22777bf047128528ba94026133..4d8671f92ef5c6a9290afadf131c51ed522078ae 100644 (file)
@@ -76,7 +76,7 @@ public class DemoApplication extends TApplication {
         item = subMenu.addItem(2002, "&Normal (sub)");
 
         if (getScreen() instanceof SwingTerminal) {
-            TMenu swingMenu = addMenu("&Swing");
+            TMenu swingMenu = addMenu("Swin&g");
             item = swingMenu.addItem(3000, "&Bigger +2");
             item = swingMenu.addItem(3001, "&Smaller -2");
         }
index f04916a1df1a933f7ab0f495dfbaae22a631f674..005383135521ac619ee2c6173737b87789b97c6b 100644 (file)
@@ -34,7 +34,7 @@ import static jexer.TCommand.*;
 import static jexer.TKeypress.*;
 
 /**
- * This window demonstates the TText, THScroller, and TVScroller widgets.
+ * This window demonstates the TEditor widget.
  */
 public class DemoEditorWindow extends TWindow {
 
@@ -56,10 +56,9 @@ public class DemoEditorWindow extends TWindow {
         super(parent, title, 0, 0, 44, 22, RESIZABLE);
         editField = addEditor(text, 0, 0, 42, 20);
 
-        statusBar = newStatusBar("Editable text window");
+        statusBar = newStatusBar("Editable text demo window");
         statusBar.addShortcutKeypress(kbF1, cmHelp, "Help");
         statusBar.addShortcutKeypress(kbF2, cmShell, "Shell");
-        statusBar.addShortcutKeypress(kbF3, cmOpen, "Open");
         statusBar.addShortcutKeypress(kbF10, cmExit, "Exit");
     }
 
index 598ac7f0ce92f889d56a78a6b42d7f41675dad02..8840605e017ef90a00fcc210d86fafd42ae250df 100644 (file)
@@ -77,7 +77,7 @@ public class DemoMainWindow extends TWindow {
     private DemoMainWindow(final TApplication parent, final int flags) {
         // Construct a demo window.  X and Y don't matter because it will be
         // centered on screen.
-        super(parent, "Demo Window", 0, 0, 60, 23, flags);
+        super(parent, "Demo Window", 0, 0, 64, 23, flags);
 
         int row = 1;
 
@@ -123,13 +123,20 @@ public class DemoMainWindow extends TWindow {
         row += 2;
 
         addLabel("Editor window", 1, row);
-        addButton("Edito&r", 35, row,
+        addButton("&1 Widget", 35, row,
             new TAction() {
                 public void DO() {
                     new DemoEditorWindow(getApplication());
                 }
             }
         );
+        addButton("&2 Window", 48, row,
+            new TAction() {
+                public void DO() {
+                    new TEditorWindow(getApplication());
+                }
+            }
+        );
         row += 2;
 
         addLabel("Text areas", 1, row);
index 5b7050fe22efd3963ca762256e163ba30c39a87b..42082e55ad21a670b31a91e8ba6e9b7e0950344e 100644 (file)
@@ -28,6 +28,9 @@
  */
 package jexer.teditor;
 
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -54,6 +57,11 @@ public class Document {
      */
     private boolean overwrite = false;
 
+    /**
+     * If true, the document has been edited.
+     */
+    private boolean dirty = false;
+
     /**
      * The default color for the TEditor class.
      */
@@ -73,6 +81,41 @@ public class Document {
         return overwrite;
     }
 
+    /**
+     * Get the dirty value.
+     *
+     * @return true if the buffer is dirty
+     */
+    public boolean isDirty() {
+        return dirty;
+    }
+
+    /**
+     * Save contents to file.
+     *
+     * @param filename file to save to
+     * @throws IOException if a java.io operation throws
+     */
+    public void saveToFilename(final String filename) throws IOException {
+        OutputStreamWriter output = null;
+        try {
+            output = new OutputStreamWriter(new FileOutputStream(filename),
+                "UTF-8");
+
+            for (Line line: lines) {
+                output.write(line.getRawString());
+                output.write("\n");
+            }
+
+            dirty = false;
+        }
+        finally {
+            if (output != null) {
+                output.close();
+            }
+        }
+    }
+
     /**
      * Set the overwrite flag.
      *
@@ -136,6 +179,15 @@ public class Document {
         return lines.get(lineNumber).getCursor();
     }
 
+    /**
+     * Set the current cursor position of the editing line.  0-based.
+     *
+     * @param cursor the new cursor position
+     */
+    public void setCursor(final int cursor) {
+        lines.get(lineNumber).setCursor(cursor);
+    }
+
     /**
      * Construct a new Document from an existing text string.
      *
@@ -280,6 +332,7 @@ public class Document {
      * Delete the character under the cursor.
      */
     public void del() {
+        dirty = true;
         lines.get(lineNumber).del();
     }
 
@@ -287,6 +340,7 @@ public class Document {
      * Delete the character immediately preceeding the cursor.
      */
     public void backspace() {
+        dirty = true;
         lines.get(lineNumber).backspace();
     }
 
@@ -297,6 +351,7 @@ public class Document {
      * @param ch the character to replace or insert
      */
     public void addChar(final char ch) {
+        dirty = true;
         if (overwrite) {
             lines.get(lineNumber).replaceChar(ch);
         } else {
index de1265982c67e3190a122a1dae0e692ba7e2a17e..d6016c22ee701c478bf86bd18597f87117b7260a 100644 (file)
@@ -60,14 +60,19 @@ public class Line {
     private int cursor = 0;
 
     /**
-     * The current word that the cursor position is in.
+     * The raw text of this line, what is passed to Word to determine
+     * highlighting behavior.
      */
-    private Word currentWord;
+    private StringBuilder rawText;
 
     /**
-     * We use getDisplayLength() a lot, so cache the value.
+     * Get a (shallow) copy of the words in this line.
+     *
+     * @return a copy of the word list
      */
-    private int displayLength = -1;
+    public List<Word> getWords() {
+        return new ArrayList<Word>(words);
+    }
 
     /**
      * Get the current cursor position.
@@ -92,39 +97,52 @@ public class Line {
                 getDisplayLength() + ", requested position " + cursor);
         }
         this.cursor = cursor;
-        // TODO: set word
     }
 
     /**
-     * Get a (shallow) copy of the list of words.
+     * Get the on-screen display length.
      *
-     * @return the list of words
+     * @return the number of cells needed to display this line
      */
-    public List<Word> getWords() {
-        return new ArrayList<Word>(words);
+    public int getDisplayLength() {
+        int n = rawText.length();
+
+        // For now just return the raw text length.
+        if (n > 0) {
+            // If we have any visible characters, add one to the display so
+            // that the cursor is immediately after the data.
+            return n + 1;
+        }
+        return n;
     }
 
     /**
-     * Get the on-screen display length.
+     * Get the raw string that matches this line.
      *
-     * @return the number of cells needed to display this line
+     * @return the string
      */
-    public int getDisplayLength() {
-        if (displayLength != -1) {
-            return displayLength;
-        }
-        int n = 0;
-        for (Word word: words) {
-            n += word.getDisplayLength();
-        }
-        displayLength = n;
+    public String getRawString() {
+        return rawText.toString();
+    }
 
-        // If we have any visible characters, add one to the display so that
-        // the cursor is immediately after the data.
-        if (displayLength > 0) {
-            displayLength++;
+    /**
+     * Scan rawText and make words out of it.
+     */
+    private void scanLine() {
+        words.clear();
+        Word word = new Word(this.defaultColor, this.highlighter);
+        words.add(word);
+        for (int i = 0; i < rawText.length(); i++) {
+            char ch = rawText.charAt(i);
+            Word newWord = word.addChar(ch);
+            if (newWord != word) {
+                words.add(newWord);
+                word = newWord;
+            }
+        }
+        for (Word w: words) {
+            w.applyHighlight();
         }
-        return displayLength;
     }
 
     /**
@@ -140,20 +158,9 @@ public class Line {
 
         this.defaultColor = defaultColor;
         this.highlighter = highlighter;
+        this.rawText = new StringBuilder(str);
 
-        currentWord = new Word(this.defaultColor, this.highlighter);
-        words.add(currentWord);
-        for (int i = 0; i < str.length(); i++) {
-            char ch = str.charAt(i);
-            Word newWord = currentWord.addChar(ch);
-            if (newWord != currentWord) {
-                words.add(newWord);
-                currentWord = newWord;
-            }
-        }
-        for (Word word: words) {
-            word.applyHighlight();
-        }
+        scanLine();
     }
 
     /**
@@ -175,7 +182,6 @@ public class Line {
         if (cursor == 0) {
             return false;
         }
-        // TODO: switch word
         cursor--;
         return true;
     }
@@ -192,7 +198,6 @@ public class Line {
         if (cursor == getDisplayLength() - 1) {
             return false;
         }
-        // TODO: switch word
         cursor++;
         return true;
     }
@@ -205,7 +210,6 @@ public class Line {
     public boolean home() {
         if (cursor > 0) {
             cursor = 0;
-            currentWord = words.get(0);
             return true;
         }
         return false;
@@ -222,7 +226,6 @@ public class Line {
             if (cursor < 0) {
                 cursor = 0;
             }
-            currentWord = words.get(words.size() - 1);
             return true;
         }
         return false;
@@ -232,14 +235,23 @@ public class Line {
      * Delete the character under the cursor.
      */
     public void del() {
-        // TODO
+        assert (words.size() > 0);
+
+        if (cursor < getDisplayLength()) {
+            rawText.deleteCharAt(cursor);
+        }
+
+        // Re-scan the line to determine the new word boundaries.
+        scanLine();
     }
 
     /**
      * Delete the character immediately preceeding the cursor.
      */
     public void backspace() {
-        // TODO
+        if (left()) {
+            del();
+        }
     }
 
     /**
@@ -248,7 +260,13 @@ public class Line {
      * @param ch the character to insert
      */
     public void addChar(final char ch) {
-        // TODO
+        if (cursor < getDisplayLength() - 1) {
+            rawText.insert(cursor, ch);
+        } else {
+            rawText.append(ch);
+        }
+        scanLine();
+        cursor++;
     }
 
     /**
@@ -257,7 +275,13 @@ public class Line {
      * @param ch the character to replace
      */
     public void replaceChar(final char ch) {
-        // TODO
+        if (cursor < getDisplayLength() - 1) {
+            rawText.setCharAt(cursor, ch);
+        } else {
+            rawText.append(ch);
+        }
+        scanLine();
+        cursor++;
     }
 
 }
index d9b3417c8eda196f222eea813e91f6158f778ebd..d4532bbfa2d720ead178d4a2efdabc35989c104a 100644 (file)
@@ -165,7 +165,8 @@ public class Word {
      * Add a character to this word.  If this is a whitespace character
      * adding to a non-whitespace word, create a new word and return that;
      * similarly if this a non-whitespace character adding to a whitespace
-     * word, create a new word and return that.
+     * word, create a new word and return that.  Note package private access:
+     * this is only called by Line to figure out highlighting boundaries.
      *
      * @param ch the new character to add
      * @return either this word (if it was added), or a new word that