changeset 10658:f7d38650fa15

8178418: TextArea/TextField: Undo removes entire text at once Reviewed-by: kcr, aghaisas Contributed-by: ambarish.rapte@oracle.com
author aghaisas
date Thu, 12 Oct 2017 14:56:23 +0530
parents dedd5afd753e
children 9a1834e21918
files modules/javafx.controls/src/main/java/javafx/scene/control/TextInputControl.java modules/javafx.controls/src/test/java/test/javafx/scene/control/TextInputControlTest.java
diffstat 2 files changed, 229 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/modules/javafx.controls/src/main/java/javafx/scene/control/TextInputControl.java	Tue Oct 10 19:49:20 2017 +0530
+++ b/modules/javafx.controls/src/main/java/javafx/scene/control/TextInputControl.java	Thu Oct 12 14:56:23 2017 +0530
@@ -570,14 +570,32 @@
             return;
         }
 
-        // If you select some stuff and type anything, then we need to
-        // create an undo record. If the range is a single character and
-        // is right next to the index of the last undo record end index, then
-        // we don't need to create a new undo record. In all other cases
-        // we do.
+        /*
+         * A new undo record is created, if
+         * 1. createNewUndoRecord is true, currently it is set to true for paste operation
+         * 2. Text is selected and a character is typed
+         * 3. This is the first operation to be added to undo record
+         * 4. forceNewUndoRecord is true, currently it is set to true if there is no text present
+         * 5. Space character is typed
+         * 6. 2500 milliseconds are elapsed since the undo record was created
+         * 7. Cursor position is changed and a character is typed
+         * 8. A range of text is replaced programmatically using replaceText()
+         * Otherwise, the last undo record is updated or discarded.
+         */
+
         int endOfUndoChange = undoChange == undoChangeHead ? -1 : undoChange.start + undoChange.newText.length();
+        boolean isNewSpaceChar = false;
+        if (newText.equals(" ")) {
+            if (!UndoRedoChange.isSpaceCharSequence()) {
+                isNewSpaceChar = true;
+                UndoRedoChange.setSpaceCharSequence(true);
+            }
+        } else {
+            UndoRedoChange.setSpaceCharSequence(false);
+        }
         if (createNewUndoRecord || nonEmptySelection || endOfUndoChange == -1 || forceNewUndoRecord ||
-                (endOfUndoChange != change.start && endOfUndoChange != change.end) || change.end - change.start > 1) {
+                isNewSpaceChar || UndoRedoChange.hasChangeDurationElapsed() ||
+                (endOfUndoChange != change.start && endOfUndoChange != change.end) || change.end - change.start > 0) {
             undoChange = undoChange.add(change.start, oldText, newText);
         } else if (change.start != change.end && change.text.isEmpty()) {
             // I know I am deleting, and am located at the end of the range of the current undo record
@@ -1467,6 +1485,9 @@
      * behavior as necessary.
      */
     static class UndoRedoChange {
+        static long prevRecordTime;
+        static final long CHANGE_DURATION = 2500; // milliseconds
+        static boolean spaceCharSequence = false;
         int start;
         String oldText;
         String newText;
@@ -1482,9 +1503,21 @@
             c.newText = newText;
             c.prev = this;
             next = c;
+            prevRecordTime = System.currentTimeMillis();
             return c;
         }
 
+        static boolean hasChangeDurationElapsed() {
+            return (System.currentTimeMillis() - prevRecordTime > CHANGE_DURATION) ;
+        }
+
+        static void setSpaceCharSequence(boolean value) {
+            spaceCharSequence = value;
+        }
+        static boolean isSpaceCharSequence() {
+            return spaceCharSequence;
+        }
+
         public UndoRedoChange discard() {
             prev.next = next;
             return prev;
--- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TextInputControlTest.java	Tue Oct 10 19:49:20 2017 +0530
+++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TextInputControlTest.java	Thu Oct 12 14:56:23 2017 +0530
@@ -1852,6 +1852,196 @@
 
         textInput.replaceText(0, 4, "");
         assertEquals("", textInput.getText());
+
+        textInput.undo();
+        assertEquals("abcd", textInput.getText());
+
+        textInput.undo();
+        assertEquals("abcefg", textInput.getText());
+
+        textInput.undo();
+        assertEquals("abcd", textInput.getText());
+
+        textInput.undo();
+        assertEquals("", textInput.getText());
+    }
+
+    // Test for JDK-8178418
+    @Test public void UndoRedoSpaceSequence() {
+        Toolkit tk = (StubToolkit)Toolkit.getToolkit();
+        StackPane root = new StackPane();
+        Scene scene = new Scene(root);
+        Stage stage = new Stage();
+        String text = "123456789";
+        String tempText = "";
+
+        textInput.setText(text);
+        stage.setScene(scene);
+        root.getChildren().removeAll();
+        root.getChildren().add(textInput);
+        stage.show();
+        tk.firePulse();
+
+        KeyEventFirer keyboard = new KeyEventFirer(textInput);
+
+        // Test sequence of spaces
+        keyboard.doKeyPress(KeyCode.HOME);
+        tk.firePulse();
+        for (int i = 0; i < 10; ++i) {
+            keyboard.doKeyTyped(KeyCode.SPACE);
+            tk.firePulse();
+            tempText += " ";
+        }
+        assertTrue(textInput.getText().equals(tempText + text));
+
+        textInput.undo();
+        assertTrue(textInput.getText().equals(text));
+
+        textInput.redo();
+        assertTrue(textInput.getText().equals(tempText + text));
+
+        root.getChildren().removeAll();
+        stage.hide();
+        tk.firePulse();
+    }
+
+    // Test for JDK-8178418
+    @Test public void UndoRedoReverseSpaceSequence() {
+        Toolkit tk = (StubToolkit)Toolkit.getToolkit();
+        StackPane root = new StackPane();
+        Scene scene = new Scene(root);
+        Stage stage = new Stage();
+        String text = "123456789";
+        String tempText = "";
+
+        textInput.setText(text);
+        stage.setScene(scene);
+        root.getChildren().removeAll();
+        root.getChildren().add(textInput);
+        stage.show();
+        tk.firePulse();
+
+        KeyEventFirer keyboard = new KeyEventFirer(textInput);
+        // Test reverse sequence of spaces
+        keyboard.doKeyPress(KeyCode.HOME);
+        tk.firePulse();
+        for (int i = 0; i < 10; ++i) {
+            keyboard.doKeyTyped(KeyCode.SPACE);
+            keyboard.doKeyPress(KeyCode.LEFT);
+            tk.firePulse();
+            tempText += " ";
+            assertTrue(textInput.getText().equals(tempText + text));
+        }
+
+        for (int i = 0; i < 10; ++i) {
+            textInput.undo();
+            tk.firePulse();
+        }
+        assertTrue(textInput.getText().equals(text));
+
+        tempText = "";
+        for (int i = 0; i < 10; ++i) {
+            textInput.redo();
+            tk.firePulse();
+            tempText += " ";
+            assertTrue(textInput.getText().equals(tempText + text));
+        }
+
+        root.getChildren().removeAll();
+        stage.hide();
+        tk.firePulse();
+    }
+
+    // Test for JDK-8178418
+    @Test public void UndoRedoWords() {
+        Toolkit tk = (StubToolkit)Toolkit.getToolkit();
+        StackPane root = new StackPane();
+        Scene scene = new Scene(root);
+        Stage stage = new Stage();
+        String text = "123456789";
+        String tempText = "";
+
+        textInput.setText(text);
+        stage.setScene(scene);
+        root.getChildren().removeAll();
+        root.getChildren().add(textInput);
+        stage.show();
+        tk.firePulse();
+
+        KeyEventFirer keyboard = new KeyEventFirer(textInput);
+
+        // Test words separated by space
+        keyboard.doKeyPress(KeyCode.HOME);
+        tk.firePulse();
+        for (int i = 0; i < 10; ++i) {
+            keyboard.doKeyTyped(KeyCode.SPACE);
+            keyboard.doKeyTyped(KeyCode.A);
+            keyboard.doKeyTyped(KeyCode.B);
+            tk.firePulse();
+            tempText += " AB";
+            assertTrue(textInput.getText().equals(tempText + text));
+        }
+
+        for (int i = 0; i < 10; ++i) {
+            textInput.undo();
+            tk.firePulse();
+        }
+        assertTrue(textInput.getText().equals(text));
+
+        tempText = "";
+        for (int i = 0; i < 10; ++i) {
+            textInput.redo();
+            tk.firePulse();
+            tempText += " AB";
+            assertTrue(textInput.getText().equals(tempText + text));
+        }
+
+        root.getChildren().removeAll();
+        stage.hide();
+        tk.firePulse();
+    }
+
+    // Test for JDK-8178418
+    @Test public void UndoRedoTimestampBased() {
+        Toolkit tk = (StubToolkit)Toolkit.getToolkit();
+        StackPane root = new StackPane();
+        Scene scene = new Scene(root);
+        Stage stage = new Stage();
+        String text = "123456789";
+        String tempText = "";
+
+        textInput.setText(text);
+        stage.setScene(scene);
+        root.getChildren().removeAll();
+        root.getChildren().add(textInput);
+        stage.show();
+        tk.firePulse();
+
+        KeyEventFirer keyboard = new KeyEventFirer(textInput);
+
+        // Test continuos sequence of characters.
+        // In this case an undo-redo record is added after 2500 milliseconds.
+        keyboard.doKeyPress(KeyCode.HOME);
+        tk.firePulse();
+
+        long startTime = System.currentTimeMillis();
+        while (System.currentTimeMillis() - startTime < 4000) {
+
+            keyboard.doKeyTyped(KeyCode.A);
+            tk.firePulse();
+            tempText += "A";
+            assertTrue(textInput.getText().equals(tempText + text));
+        }
+
+        textInput.undo();
+        assertFalse(textInput.getText().equals(text));
+        textInput.undo();
+        tk.firePulse();
+        assertTrue(textInput.getText().equals(text));
+
+        root.getChildren().removeAll();
+        stage.hide();
+        tk.firePulse();
     }
 
     // TODO tests for Content firing event notification properly