48 import javafx.beans.value.ChangeListener;
49 import javafx.beans.value.ObservableStringValue;
50 import javafx.beans.value.ObservableValue;
51 import javafx.beans.value.WritableValue;
52 import javafx.css.CssMetaData;
53 import javafx.css.FontCssMetaData;
54 import javafx.css.PseudoClass;
55 import javafx.css.StyleOrigin;
56 import javafx.css.Styleable;
57 import javafx.css.StyleableObjectProperty;
58 import javafx.css.StyleableProperty;
59 import javafx.scene.AccessibleAction;
60 import javafx.scene.AccessibleAttribute;
61 import javafx.scene.input.Clipboard;
62 import javafx.scene.input.ClipboardContent;
63 import javafx.scene.text.Font;
64
65 import java.text.BreakIterator;
66 import java.util.ArrayList;
67 import java.util.Collections;
68 import java.util.List;
69
70 import com.sun.javafx.util.Utils;
71 import com.sun.javafx.binding.ExpressionHelper;
72 import com.sun.javafx.scene.NodeHelper;
73 import javafx.util.StringConverter;
74
75 /**
76 * Abstract base class for text input controls.
77 * @since JavaFX 2.0
78 */
79 @DefaultProperty("text")
80 public abstract class TextInputControl extends Control {
81 /**
82 * Interface representing a text input's content. Since it is an ObservableStringValue,
83 * you can also bind to, or observe the content.
84 * @since JavaFX 2.0
85 */
86 protected interface Content extends ObservableStringValue {
87 /**
553 return;
554 }
555 }
556
557 // Update the content
558 updateContent(change, oldLength == 0);
559
560 }
561 }
562
563 private void updateContent(TextFormatter.Change change, boolean forceNewUndoRecord) {
564 final boolean nonEmptySelection = getSelection().getLength() > 0;
565 String oldText = getText(change.start, change.end);
566 int adjustmentAmount = replaceText(change.start, change.end, change.text, change.getAnchor(), change.getCaretPosition());
567 String newText = getText(change.start, change.start + change.text.length() - adjustmentAmount);
568 if (newText.equals(oldText)) {
569 // Undo record not required as there is no change in the text.
570 return;
571 }
572
573 // If you select some stuff and type anything, then we need to
574 // create an undo record. If the range is a single character and
575 // is right next to the index of the last undo record end index, then
576 // we don't need to create a new undo record. In all other cases
577 // we do.
578 int endOfUndoChange = undoChange == undoChangeHead ? -1 : undoChange.start + undoChange.newText.length();
579 if (createNewUndoRecord || nonEmptySelection || endOfUndoChange == -1 || forceNewUndoRecord ||
580 (endOfUndoChange != change.start && endOfUndoChange != change.end) || change.end - change.start > 1) {
581 undoChange = undoChange.add(change.start, oldText, newText);
582 } else if (change.start != change.end && change.text.isEmpty()) {
583 // I know I am deleting, and am located at the end of the range of the current undo record
584 if (undoChange.newText.length() > 0) {
585 undoChange.newText = undoChange.newText.substring(0, change.start - undoChange.start);
586 if (undoChange.newText.isEmpty()) {
587 // throw away this undo change record
588 undoChange = undoChange.discard();
589 }
590 } else {
591 if (change.start == endOfUndoChange) {
592 undoChange.oldText += oldText;
593 } else { // end == endOfUndoChange
594 undoChange.oldText = oldText + undoChange.oldText;
595 undoChange.start--;
596 }
597 }
598 } else {
599 // I know I am adding, and am located at the end of the range of the current undo record
600 undoChange.newText += newText;
1450 public void invalidated(Observable valueModel) {
1451 // We now need to force it to be eagerly recomputed
1452 // because we need to push these changes to the
1453 // content model. Because changing the model ends
1454 // up calling invalidate and markInvalid, the
1455 // listeners will all be notified.
1456 doSet(observable.getValue());
1457 }
1458 }
1459 }
1460
1461 /**
1462 * Used to form a linked-list of Undo / Redo changes. Each UndoRedoChange
1463 * records the old and new text, and the start index. It also has
1464 * the links to the previous and next Changes in the chain. There
1465 * are two special UndoRedoChange objects in this chain representing the
1466 * head and the tail so we can have beforeFirst and afterLast
1467 * behavior as necessary.
1468 */
1469 static class UndoRedoChange {
1470 int start;
1471 String oldText;
1472 String newText;
1473 UndoRedoChange prev;
1474 UndoRedoChange next;
1475
1476 UndoRedoChange() { }
1477
1478 public UndoRedoChange add(int start, String oldText, String newText) {
1479 UndoRedoChange c = new UndoRedoChange();
1480 c.start = start;
1481 c.oldText = oldText;
1482 c.newText = newText;
1483 c.prev = this;
1484 next = c;
1485 return c;
1486 }
1487
1488 public UndoRedoChange discard() {
1489 prev.next = next;
1490 return prev;
1491 }
1492
1493 // Handy to use when debugging, just put it in undo or redo
1494 // method or replaceText to see what is happening to the undo
1495 // history as it occurs.
1496 void debugPrint() {
1497 UndoRedoChange c = this;
1498 System.out.print("[");
1499 while (c != null) {
1500 System.out.print(c.toString());
1501 if (c.next != null) System.out.print(", ");
1502 c = c.next;
1503 }
1504 System.out.println("]");
1505 }
|
48 import javafx.beans.value.ChangeListener;
49 import javafx.beans.value.ObservableStringValue;
50 import javafx.beans.value.ObservableValue;
51 import javafx.beans.value.WritableValue;
52 import javafx.css.CssMetaData;
53 import javafx.css.FontCssMetaData;
54 import javafx.css.PseudoClass;
55 import javafx.css.StyleOrigin;
56 import javafx.css.Styleable;
57 import javafx.css.StyleableObjectProperty;
58 import javafx.css.StyleableProperty;
59 import javafx.scene.AccessibleAction;
60 import javafx.scene.AccessibleAttribute;
61 import javafx.scene.input.Clipboard;
62 import javafx.scene.input.ClipboardContent;
63 import javafx.scene.text.Font;
64
65 import java.text.BreakIterator;
66 import java.util.ArrayList;
67 import java.util.Collections;
68 import java.util.Date;
69 import java.util.List;
70
71 import com.sun.javafx.util.Utils;
72 import com.sun.javafx.binding.ExpressionHelper;
73 import com.sun.javafx.scene.NodeHelper;
74 import javafx.util.StringConverter;
75
76 /**
77 * Abstract base class for text input controls.
78 * @since JavaFX 2.0
79 */
80 @DefaultProperty("text")
81 public abstract class TextInputControl extends Control {
82 /**
83 * Interface representing a text input's content. Since it is an ObservableStringValue,
84 * you can also bind to, or observe the content.
85 * @since JavaFX 2.0
86 */
87 protected interface Content extends ObservableStringValue {
88 /**
554 return;
555 }
556 }
557
558 // Update the content
559 updateContent(change, oldLength == 0);
560
561 }
562 }
563
564 private void updateContent(TextFormatter.Change change, boolean forceNewUndoRecord) {
565 final boolean nonEmptySelection = getSelection().getLength() > 0;
566 String oldText = getText(change.start, change.end);
567 int adjustmentAmount = replaceText(change.start, change.end, change.text, change.getAnchor(), change.getCaretPosition());
568 String newText = getText(change.start, change.start + change.text.length() - adjustmentAmount);
569 if (newText.equals(oldText)) {
570 // Undo record not required as there is no change in the text.
571 return;
572 }
573
574 /*
575 * A new undo record is created, if
576 * 1. createNewUndoRecord is true, currently it is set to true for paste operation
577 * 2. Text is selected and a character is typed
578 * 3. This is the first operation to be added to undo record
579 * 4. forceNewUndoRecord is true, currently it is set to true if there is no text present
580 * 5. Space character is typed
581 * 6. 2500 milliseconds are elapsed since the undo record was created
582 * 7. Cursor position is changed and a character is typed
583 * 8. A range of text is replaced programmatically using replaceText()
584 * Otherwise, the last undo record is updated or discarded.
585 */
586
587 int endOfUndoChange = undoChange == undoChangeHead ? -1 : undoChange.start + undoChange.newText.length();
588 boolean isNewSpaceChar = false;
589 if (newText.equals(" ")) {
590 if (UndoRedoChange.isSpaceCharSequence()) {
591 isNewSpaceChar = false;
592 } else {
593 isNewSpaceChar = true;
594 UndoRedoChange.setSpaceCharSequence(true);
595 }
596 } else {
597 UndoRedoChange.setSpaceCharSequence(false);
598 }
599 if (createNewUndoRecord || nonEmptySelection || endOfUndoChange == -1 || forceNewUndoRecord ||
600 isNewSpaceChar || UndoRedoChange.ifChangeDurationElapsed() ||
601 (endOfUndoChange != change.start && endOfUndoChange != change.end) || change.end - change.start > 0) {
602 undoChange = undoChange.add(change.start, oldText, newText);
603 } else if (change.start != change.end && change.text.isEmpty()) {
604 // I know I am deleting, and am located at the end of the range of the current undo record
605 if (undoChange.newText.length() > 0) {
606 undoChange.newText = undoChange.newText.substring(0, change.start - undoChange.start);
607 if (undoChange.newText.isEmpty()) {
608 // throw away this undo change record
609 undoChange = undoChange.discard();
610 }
611 } else {
612 if (change.start == endOfUndoChange) {
613 undoChange.oldText += oldText;
614 } else { // end == endOfUndoChange
615 undoChange.oldText = oldText + undoChange.oldText;
616 undoChange.start--;
617 }
618 }
619 } else {
620 // I know I am adding, and am located at the end of the range of the current undo record
621 undoChange.newText += newText;
1471 public void invalidated(Observable valueModel) {
1472 // We now need to force it to be eagerly recomputed
1473 // because we need to push these changes to the
1474 // content model. Because changing the model ends
1475 // up calling invalidate and markInvalid, the
1476 // listeners will all be notified.
1477 doSet(observable.getValue());
1478 }
1479 }
1480 }
1481
1482 /**
1483 * Used to form a linked-list of Undo / Redo changes. Each UndoRedoChange
1484 * records the old and new text, and the start index. It also has
1485 * the links to the previous and next Changes in the chain. There
1486 * are two special UndoRedoChange objects in this chain representing the
1487 * head and the tail so we can have beforeFirst and afterLast
1488 * behavior as necessary.
1489 */
1490 static class UndoRedoChange {
1491 static long prevRecordTime;
1492 static final long CHANGE_DURATION = 2500; // milliseconds
1493 static boolean spaceCharSequence = false;
1494 int start;
1495 String oldText;
1496 String newText;
1497 UndoRedoChange prev;
1498 UndoRedoChange next;
1499
1500 UndoRedoChange() { }
1501
1502 public UndoRedoChange add(int start, String oldText, String newText) {
1503 UndoRedoChange c = new UndoRedoChange();
1504 c.start = start;
1505 c.oldText = oldText;
1506 c.newText = newText;
1507 c.prev = this;
1508 next = c;
1509 prevRecordTime = (new Date()).getTime();
1510 return c;
1511 }
1512
1513 static boolean ifChangeDurationElapsed() {
1514 return ((new Date()).getTime() - prevRecordTime > CHANGE_DURATION) ;
1515 }
1516
1517 static void setSpaceCharSequence(boolean value) {
1518 spaceCharSequence = value;
1519 }
1520 static boolean isSpaceCharSequence() {
1521 return spaceCharSequence;
1522 }
1523
1524 public UndoRedoChange discard() {
1525 prev.next = next;
1526 return prev;
1527 }
1528
1529 // Handy to use when debugging, just put it in undo or redo
1530 // method or replaceText to see what is happening to the undo
1531 // history as it occurs.
1532 void debugPrint() {
1533 UndoRedoChange c = this;
1534 System.out.print("[");
1535 while (c != null) {
1536 System.out.print(c.toString());
1537 if (c.next != null) System.out.print(", ");
1538 c = c.next;
1539 }
1540 System.out.println("]");
1541 }
|