diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/BoundedSelection.java b/richtextfx/src/main/java/org/fxmisc/richtext/BoundedSelection.java index d1b9b23a9..09c9ba5b5 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/BoundedSelection.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/BoundedSelection.java @@ -44,6 +44,9 @@ * then just write the much simpler {@code BoundedSelection selection}. *

* + * @see Caret + * @see UnboundedSelection + * * @param type for {@link StyledDocument}'s paragraph style; only necessary when using the "selectedDocument" * getter or property * @param type for {@link StyledDocument}'s segment type; only necessary when using the "selectedDocument" diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/BoundedSelectionImpl.java b/richtextfx/src/main/java/org/fxmisc/richtext/BoundedSelectionImpl.java index 876fb491a..6e27d2817 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/BoundedSelectionImpl.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/BoundedSelectionImpl.java @@ -16,7 +16,7 @@ import java.util.Optional; -public class BoundedSelectionImpl implements BoundedSelection { +final class BoundedSelectionImpl implements BoundedSelection { private final UnboundedSelection delegate; @Override public ObservableValue rangeProperty() { return delegate.rangeProperty(); } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java index 41615d980..8749891e1 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java @@ -315,52 +315,6 @@ private static int clamp(int min, int val, int max) { @Override public Duration getMouseOverTextDelay() { return mouseOverTextDelay.get(); } @Override public ObjectProperty mouseOverTextDelayProperty() { return mouseOverTextDelay; } - private final BooleanProperty autoScrollOnDragDesired = new SimpleBooleanProperty(true); - public final void setAutoScrollOnDragDesired(boolean val) { autoScrollOnDragDesired.set(val); } - public final boolean isAutoScrollOnDragDesired() { return autoScrollOnDragDesired.get(); } - - private final Property> onOutsideSelectionMousePress = new SimpleObjectProperty<>(e -> { - CharacterHit hit = hit(e.getX(), e.getY()); - moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR); - }); - public final void setOnOutsideSelectionMousePress(Consumer consumer) { onOutsideSelectionMousePress.setValue(consumer); } - public final Consumer getOnOutsideSelectionMousePress() { return onOutsideSelectionMousePress.getValue(); } - - private final Property> onInsideSelectionMousePressRelease = new SimpleObjectProperty<>(e -> { - CharacterHit hit = hit(e.getX(), e.getY()); - moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR); - }); - public final void setOnInsideSelectionMousePressRelease(Consumer consumer) { onInsideSelectionMousePressRelease.setValue(consumer); } - public final Consumer getOnInsideSelectionMousePressRelease() { return onInsideSelectionMousePressRelease.getValue(); } - - private final Property> onNewSelectionDrag = new SimpleObjectProperty<>(p -> { - CharacterHit hit = hit(p.getX(), p.getY()); - moveTo(hit.getInsertionIndex(), SelectionPolicy.ADJUST); - }); - public final void setOnNewSelectionDrag(Consumer consumer) { onNewSelectionDrag.setValue(consumer); } - public final Consumer getOnNewSelectionDrag() { return onNewSelectionDrag.getValue(); } - - private final Property> onNewSelectionDragEnd = new SimpleObjectProperty<>(e -> { - CharacterHit hit = hit(e.getX(), e.getY()); - moveTo(hit.getInsertionIndex(), SelectionPolicy.ADJUST); - }); - public final void setOnNewSelectionDragEnd(Consumer consumer) { onNewSelectionDragEnd.setValue(consumer); } - public final Consumer getOnNewSelectionDragEnd() { return onNewSelectionDragEnd.getValue(); } - - private final Property> onSelectionDrag = new SimpleObjectProperty<>(p -> { - CharacterHit hit = hit(p.getX(), p.getY()); - displaceCaret(hit.getInsertionIndex()); - }); - public final void setOnSelectionDrag(Consumer consumer) { onSelectionDrag.setValue(consumer); } - public final Consumer getOnSelectionDrag() { return onSelectionDrag.getValue(); } - - private final Property> onSelectionDrop = new SimpleObjectProperty<>(e -> { - CharacterHit hit = hit(e.getX(), e.getY()); - moveSelectedText(hit.getInsertionIndex()); - }); - @Override public final void setOnSelectionDrop(Consumer consumer) { onSelectionDrop.setValue(consumer); } - @Override public final Consumer getOnSelectionDrop() { return onSelectionDrop.getValue(); } - private final ObjectProperty> paragraphGraphicFactory = new SimpleObjectProperty<>(null); @Override public void setParagraphGraphicFactory(IntFunction factory) { paragraphGraphicFactory.set(factory); } @@ -415,6 +369,60 @@ public Optional, Codec>> getStyleCodecs() { @Override public void setEstimatedScrollY(double value) { virtualFlow.estimatedScrollYProperty().setValue(value); } + /* ********************************************************************** * + * * + * Mouse Behavior Hooks * + * * + * Hooks for overriding some of the default mouse behavior * + * * + * ********************************************************************** */ + + private final Property> onOutsideSelectionMousePress = new SimpleObjectProperty<>(e -> { + CharacterHit hit = hit(e.getX(), e.getY()); + moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR); + }); + public final void setOnOutsideSelectionMousePress(Consumer consumer) { onOutsideSelectionMousePress.setValue(consumer); } + public final Consumer getOnOutsideSelectionMousePress() { return onOutsideSelectionMousePress.getValue(); } + + private final Property> onInsideSelectionMousePressRelease = new SimpleObjectProperty<>(e -> { + CharacterHit hit = hit(e.getX(), e.getY()); + moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR); + }); + public final void setOnInsideSelectionMousePressRelease(Consumer consumer) { onInsideSelectionMousePressRelease.setValue(consumer); } + public final Consumer getOnInsideSelectionMousePressRelease() { return onInsideSelectionMousePressRelease.getValue(); } + + private final Property> onNewSelectionDrag = new SimpleObjectProperty<>(p -> { + CharacterHit hit = hit(p.getX(), p.getY()); + moveTo(hit.getInsertionIndex(), SelectionPolicy.ADJUST); + }); + public final void setOnNewSelectionDrag(Consumer consumer) { onNewSelectionDrag.setValue(consumer); } + public final Consumer getOnNewSelectionDrag() { return onNewSelectionDrag.getValue(); } + + private final Property> onNewSelectionDragEnd = new SimpleObjectProperty<>(e -> { + CharacterHit hit = hit(e.getX(), e.getY()); + moveTo(hit.getInsertionIndex(), SelectionPolicy.ADJUST); + }); + public final void setOnNewSelectionDragEnd(Consumer consumer) { onNewSelectionDragEnd.setValue(consumer); } + public final Consumer getOnNewSelectionDragEnd() { return onNewSelectionDragEnd.getValue(); } + + private final Property> onSelectionDrag = new SimpleObjectProperty<>(p -> { + CharacterHit hit = hit(p.getX(), p.getY()); + displaceCaret(hit.getInsertionIndex()); + }); + public final void setOnSelectionDrag(Consumer consumer) { onSelectionDrag.setValue(consumer); } + public final Consumer getOnSelectionDrag() { return onSelectionDrag.getValue(); } + + private final Property> onSelectionDrop = new SimpleObjectProperty<>(e -> { + CharacterHit hit = hit(e.getX(), e.getY()); + moveSelectedText(hit.getInsertionIndex()); + }); + @Override public final void setOnSelectionDrop(Consumer consumer) { onSelectionDrop.setValue(consumer); } + @Override public final Consumer getOnSelectionDrop() { return onSelectionDrop.getValue(); } + + // not a hook, but still plays a part in the default mouse behavior + private final BooleanProperty autoScrollOnDragDesired = new SimpleBooleanProperty(true); + public final void setAutoScrollOnDragDesired(boolean val) { autoScrollOnDragDesired.set(val); } + public final boolean isAutoScrollOnDragDesired() { return autoScrollOnDragDesired.get(); } /* ********************************************************************** * * * @@ -615,7 +623,7 @@ public GenericStyledArea( this.applyParagraphStyle = applyParagraphStyle; this.segmentOps = segmentOps; - undoManager = UndoUtils.createUndoManager(this); + undoManager = UndoUtils.defaultUndoManager(this); // allow tab traversal into area setFocusTraversable(true); @@ -647,13 +655,6 @@ public GenericStyledArea( IntUnaryOperator cellLength = i -> virtualFlow.getCell(i).getNode().getLineCount(); navigator = new TwoLevelNavigator(cellCount, cellLength); - // relayout the popup when any of its settings values change (besides the caret being dirty) - EventStream popupAlignmentDirty = invalidationsOf(popupAlignmentProperty()); - EventStream popupAnchorAdjustmentDirty = invalidationsOf(popupAnchorAdjustmentProperty()); - EventStream popupAnchorOffsetDirty = invalidationsOf(popupAnchorOffsetProperty()); - EventStream popupDirty = merge(popupAlignmentDirty, popupAnchorAdjustmentDirty, popupAnchorOffsetDirty); - subscribeTo(popupDirty, x -> layoutPopup()); - viewportDirty = merge( // no need to check for width & height invalidations as scroll values update when these do @@ -677,17 +678,6 @@ public GenericStyledArea( visibleParagraphs = LiveList.map(virtualFlow.visibleCells(), c -> c.getNode().getParagraph()).suspendable(); - // Adjust popup anchor by either a user-provided function, - // or user-provided offset, or don't adjust at all. - Val> userOffset = Val.map( - popupAnchorOffsetProperty(), - offset -> anchor -> anchor.add(offset)); - _popupAnchorAdjustment = - Val.orElse( - popupAnchorAdjustmentProperty(), - userOffset) - .orElseConst(UnaryOperator.identity()); - final Suspendable omniSuspendable = Suspendable.combine( beingUpdated, // must be first, to be the last one to release @@ -703,6 +693,26 @@ public GenericStyledArea( .subscribe(evt -> Event.fireEvent(this, evt)); new StyledTextAreaBehavior(this); + + // Code below this point is deprecated Popup API. It will be removed in the future + + // relayout the popup when any of its settings values change (besides the caret being dirty) + EventStream popupAlignmentDirty = invalidationsOf(popupAlignmentProperty()); + EventStream popupAnchorAdjustmentDirty = invalidationsOf(popupAnchorAdjustmentProperty()); + EventStream popupAnchorOffsetDirty = invalidationsOf(popupAnchorOffsetProperty()); + EventStream popupDirty = merge(popupAlignmentDirty, popupAnchorAdjustmentDirty, popupAnchorOffsetDirty); + subscribeTo(popupDirty, x -> layoutPopup()); + + // Adjust popup anchor by either a user-provided function, + // or user-provided offset, or don't adjust at all. + Val> userOffset = Val.map( + popupAnchorOffsetProperty(), + offset -> anchor -> anchor.add(offset)); + _popupAnchorAdjustment = + Val.orElse( + popupAnchorAdjustmentProperty(), + userOffset) + .orElseConst(UnaryOperator.identity()); } /* ********************************************************************** * diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/UnboundedSelectionImpl.java b/richtextfx/src/main/java/org/fxmisc/richtext/UnboundedSelectionImpl.java index 12293ac67..b34bc4066 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/UnboundedSelectionImpl.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/UnboundedSelectionImpl.java @@ -25,7 +25,7 @@ import static org.reactfx.EventStreams.invalidationsOf; import static org.reactfx.EventStreams.merge; -public final class UnboundedSelectionImpl implements UnboundedSelection { +final class UnboundedSelectionImpl implements UnboundedSelection { private final SuspendableVal range; @Override public final IndexRange getRange() { return range.getValue(); } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/util/UndoUtils.java b/richtextfx/src/main/java/org/fxmisc/richtext/util/UndoUtils.java index 9c6fe170f..39088ca1f 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/util/UndoUtils.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/util/UndoUtils.java @@ -9,35 +9,85 @@ import java.util.function.Consumer; +/** + * A class filled with factory methods to help easily construct an {@link UndoManager} for a {@link GenericStyledArea}. + */ public final class UndoUtils { private UndoUtils() { throw new IllegalStateException("UndoUtils cannot be instantiated"); } - public static UndoManager createUndoManager(GenericStyledArea area) { + /** + * Constructs an UndoManager with an unlimited history: + * if {@link GenericStyledArea#isPreserveStyle() the area's preserveStyle flag is true}, the returned UndoManager + * can undo/redo {@link RichTextChange}s; otherwise, it can undo/redo {@link PlainTextChange}s. + */ + public static UndoManager defaultUndoManager(GenericStyledArea area) { return area.isPreserveStyle() ? richTextUndoManager(area) : plainTextUndoManager(area); } - public static UndoManager richTextUndoManager(GenericStyledArea area) { + /* ********************************************************************** * + * * + * UndoManager Factory Methods * + * * + * Code that constructs different kinds of UndoManagers for an area * + * * + * ********************************************************************** */ + + /** + * Returns an UndoManager with an unlimited history that can undo/redo {@link RichTextChange}s. + */ + public static UndoManager> richTextUndoManager(GenericStyledArea area) { return richTextUndoManager(area, UndoManagerFactory.unlimitedHistoryFactory()); } - public static UndoManager richTextUndoManager(GenericStyledArea area, + /** + * Returns an UndoManager that can undo/redo {@link RichTextChange}s. + */ + public static UndoManager> richTextUndoManager(GenericStyledArea area, UndoManagerFactory factory) { - Consumer> apply = change -> area.replace(change.getPosition(), change.getPosition() + change.getRemoved().length(), change.getInserted()); - return factory.create(area.richChanges(), RichTextChange::invert, apply, TextChange::mergeWith, TextChange::isIdentity); + return factory.create(area.richChanges(), TextChange::invert, applyRichTextChange(area), TextChange::mergeWith, TextChange::isIdentity); }; - public static UndoManager plainTextUndoManager(GenericStyledArea area) { + /** + * Returns an UndoManager with an unlimited history that can undo/redo {@link PlainTextChange}s. + */ + public static UndoManager plainTextUndoManager(GenericStyledArea area) { return plainTextUndoManager(area, UndoManagerFactory.unlimitedHistoryFactory()); } - public static UndoManager plainTextUndoManager(GenericStyledArea area, + /** + * Returns an UndoManager that can undo/redo {@link PlainTextChange}s. + */ + public static UndoManager plainTextUndoManager(GenericStyledArea area, UndoManagerFactory factory) { - Consumer apply = change -> area.replaceText(change.getPosition(), change.getPosition() + change.getRemoved().length(), change.getInserted()); - return factory.create(area.plainTextChanges(), PlainTextChange::invert, apply, TextChange::mergeWith, TextChange::isIdentity); + return factory.create(area.plainTextChanges(), TextChange::invert, applyPlainTextChange(area), TextChange::mergeWith, TextChange::isIdentity); + } + + /* ********************************************************************** * + * * + * Change Appliers * + * * + * Code that handles how a change should be applied to the area * + * * + * ********************************************************************** */ + + /** + * Applies a {@link PlainTextChange} to the given area when the {@link UndoManager}'s change stream emits an event + * by {@code area.replaceText(change.getPosition(), change.getRemovalEnd(), change.getInserted()}. + */ + public static Consumer applyPlainTextChange(GenericStyledArea area) { + return change -> area.replaceText(change.getPosition(), change.getRemovalEnd(), change.getInserted()); + } + + /** + * Applies a {@link PlainTextChange} to the given area when the {@link UndoManager}'s change stream emits an event + * by {@code area.replace(change.getPosition(), change.getRemovalEnd(), change.getInserted()}. + */ + public static Consumer> applyRichTextChange(GenericStyledArea area) { + return change -> area.replace(change.getPosition(), change.getRemovalEnd(), change.getInserted()); } }