Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reposition code and refactor / add javadoc to #526 & #528 #529

Merged
merged 7 commits into from
Jun 22, 2017
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
* then just write the much simpler {@code BoundedSelection<?, ?, ?> selection}</b>.
* </p>
*
* @see Caret
* @see UnboundedSelection
*
* @param <PS> type for {@link StyledDocument}'s paragraph style; only necessary when using the "selectedDocument"
* getter or property
* @param <SEG> type for {@link StyledDocument}'s segment type; only necessary when using the "selectedDocument"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import java.util.Optional;

public class BoundedSelectionImpl<PS, SEG, S> implements BoundedSelection<PS, SEG, S> {
final class BoundedSelectionImpl<PS, SEG, S> implements BoundedSelection<PS, SEG, S> {

private final UnboundedSelection<PS, SEG, S> delegate;
@Override public ObservableValue<IndexRange> rangeProperty() { return delegate.rangeProperty(); }
Expand Down
140 changes: 75 additions & 65 deletions richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java
Original file line number Diff line number Diff line change
Expand Up @@ -315,52 +315,6 @@ private static int clamp(int min, int val, int max) {
@Override public Duration getMouseOverTextDelay() { return mouseOverTextDelay.get(); }
@Override public ObjectProperty<Duration> 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<Consumer<MouseEvent>> onOutsideSelectionMousePress = new SimpleObjectProperty<>(e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
});
public final void setOnOutsideSelectionMousePress(Consumer<MouseEvent> consumer) { onOutsideSelectionMousePress.setValue(consumer); }
public final Consumer<MouseEvent> getOnOutsideSelectionMousePress() { return onOutsideSelectionMousePress.getValue(); }

private final Property<Consumer<MouseEvent>> onInsideSelectionMousePressRelease = new SimpleObjectProperty<>(e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
});
public final void setOnInsideSelectionMousePressRelease(Consumer<MouseEvent> consumer) { onInsideSelectionMousePressRelease.setValue(consumer); }
public final Consumer<MouseEvent> getOnInsideSelectionMousePressRelease() { return onInsideSelectionMousePressRelease.getValue(); }

private final Property<Consumer<Point2D>> onNewSelectionDrag = new SimpleObjectProperty<>(p -> {
CharacterHit hit = hit(p.getX(), p.getY());
moveTo(hit.getInsertionIndex(), SelectionPolicy.ADJUST);
});
public final void setOnNewSelectionDrag(Consumer<Point2D> consumer) { onNewSelectionDrag.setValue(consumer); }
public final Consumer<Point2D> getOnNewSelectionDrag() { return onNewSelectionDrag.getValue(); }

private final Property<Consumer<MouseEvent>> onNewSelectionDragEnd = new SimpleObjectProperty<>(e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveTo(hit.getInsertionIndex(), SelectionPolicy.ADJUST);
});
public final void setOnNewSelectionDragEnd(Consumer<MouseEvent> consumer) { onNewSelectionDragEnd.setValue(consumer); }
public final Consumer<MouseEvent> getOnNewSelectionDragEnd() { return onNewSelectionDragEnd.getValue(); }

private final Property<Consumer<Point2D>> onSelectionDrag = new SimpleObjectProperty<>(p -> {
CharacterHit hit = hit(p.getX(), p.getY());
displaceCaret(hit.getInsertionIndex());
});
public final void setOnSelectionDrag(Consumer<Point2D> consumer) { onSelectionDrag.setValue(consumer); }
public final Consumer<Point2D> getOnSelectionDrag() { return onSelectionDrag.getValue(); }

private final Property<Consumer<MouseEvent>> onSelectionDrop = new SimpleObjectProperty<>(e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveSelectedText(hit.getInsertionIndex());
});
@Override public final void setOnSelectionDrop(Consumer<MouseEvent> consumer) { onSelectionDrop.setValue(consumer); }
@Override public final Consumer<MouseEvent> getOnSelectionDrop() { return onSelectionDrop.getValue(); }

private final ObjectProperty<IntFunction<? extends Node>> paragraphGraphicFactory = new SimpleObjectProperty<>(null);
@Override
public void setParagraphGraphicFactory(IntFunction<? extends Node> factory) { paragraphGraphicFactory.set(factory); }
Expand Down Expand Up @@ -415,6 +369,60 @@ public Optional<Tuple2<Codec<PS>, Codec<SEG>>> 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<Consumer<MouseEvent>> onOutsideSelectionMousePress = new SimpleObjectProperty<>(e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
});
public final void setOnOutsideSelectionMousePress(Consumer<MouseEvent> consumer) { onOutsideSelectionMousePress.setValue(consumer); }
public final Consumer<MouseEvent> getOnOutsideSelectionMousePress() { return onOutsideSelectionMousePress.getValue(); }

private final Property<Consumer<MouseEvent>> onInsideSelectionMousePressRelease = new SimpleObjectProperty<>(e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
});
public final void setOnInsideSelectionMousePressRelease(Consumer<MouseEvent> consumer) { onInsideSelectionMousePressRelease.setValue(consumer); }
public final Consumer<MouseEvent> getOnInsideSelectionMousePressRelease() { return onInsideSelectionMousePressRelease.getValue(); }

private final Property<Consumer<Point2D>> onNewSelectionDrag = new SimpleObjectProperty<>(p -> {
CharacterHit hit = hit(p.getX(), p.getY());
moveTo(hit.getInsertionIndex(), SelectionPolicy.ADJUST);
});
public final void setOnNewSelectionDrag(Consumer<Point2D> consumer) { onNewSelectionDrag.setValue(consumer); }
public final Consumer<Point2D> getOnNewSelectionDrag() { return onNewSelectionDrag.getValue(); }

private final Property<Consumer<MouseEvent>> onNewSelectionDragEnd = new SimpleObjectProperty<>(e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveTo(hit.getInsertionIndex(), SelectionPolicy.ADJUST);
});
public final void setOnNewSelectionDragEnd(Consumer<MouseEvent> consumer) { onNewSelectionDragEnd.setValue(consumer); }
public final Consumer<MouseEvent> getOnNewSelectionDragEnd() { return onNewSelectionDragEnd.getValue(); }

private final Property<Consumer<Point2D>> onSelectionDrag = new SimpleObjectProperty<>(p -> {
CharacterHit hit = hit(p.getX(), p.getY());
displaceCaret(hit.getInsertionIndex());
});
public final void setOnSelectionDrag(Consumer<Point2D> consumer) { onSelectionDrag.setValue(consumer); }
public final Consumer<Point2D> getOnSelectionDrag() { return onSelectionDrag.getValue(); }

private final Property<Consumer<MouseEvent>> onSelectionDrop = new SimpleObjectProperty<>(e -> {
CharacterHit hit = hit(e.getX(), e.getY());
moveSelectedText(hit.getInsertionIndex());
});
@Override public final void setOnSelectionDrop(Consumer<MouseEvent> consumer) { onSelectionDrop.setValue(consumer); }
@Override public final Consumer<MouseEvent> 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(); }

/* ********************************************************************** *
* *
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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

Expand All @@ -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<UnaryOperator<Point2D>> 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

Expand All @@ -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<UnaryOperator<Point2D>> userOffset = Val.map(
popupAnchorOffsetProperty(),
offset -> anchor -> anchor.add(offset));
_popupAnchorAdjustment =
Val.orElse(
popupAnchorAdjustmentProperty(),
userOffset)
.orElseConst(UnaryOperator.identity());
}

/* ********************************************************************** *
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import static org.reactfx.EventStreams.invalidationsOf;
import static org.reactfx.EventStreams.merge;

public final class UnboundedSelectionImpl<PS, SEG, S> implements UnboundedSelection<PS, SEG, S> {
final class UnboundedSelectionImpl<PS, SEG, S> implements UnboundedSelection<PS, SEG, S> {

private final SuspendableVal<IndexRange> range;
@Override public final IndexRange getRange() { return range.getValue(); }
Expand Down
68 changes: 59 additions & 9 deletions richtextfx/src/main/java/org/fxmisc/richtext/util/UndoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <PS, SEG, S> UndoManager createUndoManager(GenericStyledArea<PS, SEG, S> 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 <PS, SEG, S> UndoManager defaultUndoManager(GenericStyledArea<PS, SEG, S> area) {
return area.isPreserveStyle()
? richTextUndoManager(area)
: plainTextUndoManager(area);
}

public static <PS, SEG, S> UndoManager richTextUndoManager(GenericStyledArea<PS, SEG, S> 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 <PS, SEG, S> UndoManager<RichTextChange<PS, SEG, S>> richTextUndoManager(GenericStyledArea<PS, SEG, S> area) {
return richTextUndoManager(area, UndoManagerFactory.unlimitedHistoryFactory());
}

public static <PS, SEG, S> UndoManager richTextUndoManager(GenericStyledArea<PS, SEG, S> area,
/**
* Returns an UndoManager that can undo/redo {@link RichTextChange}s.
*/
public static <PS, SEG, S> UndoManager<RichTextChange<PS, SEG, S>> richTextUndoManager(GenericStyledArea<PS, SEG, S> area,
UndoManagerFactory factory) {
Consumer<RichTextChange<PS, SEG, S>> 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 <PS, SEG, S> UndoManager plainTextUndoManager(GenericStyledArea<PS, SEG, S> area) {
/**
* Returns an UndoManager with an unlimited history that can undo/redo {@link PlainTextChange}s.
*/
public static <PS, SEG, S> UndoManager<PlainTextChange> plainTextUndoManager(GenericStyledArea<PS, SEG, S> area) {
return plainTextUndoManager(area, UndoManagerFactory.unlimitedHistoryFactory());
}

public static <PS, SEG, S> UndoManager plainTextUndoManager(GenericStyledArea<PS, SEG, S> area,
/**
* Returns an UndoManager that can undo/redo {@link PlainTextChange}s.
*/
public static <PS, SEG, S> UndoManager<PlainTextChange> plainTextUndoManager(GenericStyledArea<PS, SEG, S> area,
UndoManagerFactory factory) {
Consumer<PlainTextChange> 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 <PS, SEG, S> Consumer<PlainTextChange> applyPlainTextChange(GenericStyledArea<PS, SEG, S> 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 <PS, SEG, S> Consumer<RichTextChange<PS, SEG, S>> applyRichTextChange(GenericStyledArea<PS, SEG, S> area) {
return change -> area.replace(change.getPosition(), change.getRemovalEnd(), change.getInserted());
}
}