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

Attach and detach control from viewmodel, including test #330

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 73 additions & 51 deletions rta/src/main/java/com/gluonhq/richtextarea/RichTextAreaSkin.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
Expand Down Expand Up @@ -361,7 +365,53 @@ protected void invalidated() {
private int nonTextNodesCount;
AtomicInteger nonTextNodes = new AtomicInteger();

// attachedProperty
private final BooleanProperty attachedProperty = new SimpleBooleanProperty() {
@Override
protected void invalidated() {
if (promptVisibleBinding == null) {
promptVisibleBinding = Bindings.createBooleanBinding(
() -> {
Point2D point2D = caretOriginProperty.get();
boolean visible = viewModel.getTextLength() == 0 && viewModel.getCaretPosition() == 0 &&
point2D.getX() > DEFAULT_POINT_2D.getX() && point2D.getY() > DEFAULT_POINT_2D.getY();
if (visible) {
updatePromptNodeLocation();
}
return visible;
},
viewModel.caretPositionProperty(), viewModel.textLengthProperty(), caretOriginProperty);
}

if (get()) {
// bind control properties to viewModel properties, to forward the changes of the later
getSkinnable().textLengthProperty.bind(viewModel.textLengthProperty());
getSkinnable().modifiedProperty.bind(viewModel.savedProperty().not());
getSkinnable().selectionProperty.bind(viewModel.selectionProperty());
getSkinnable().decorationAtCaret.bind(viewModel.decorationAtCaretProperty());
getSkinnable().decorationAtParagraph.bind(viewModel.decorationAtParagraphProperty());
caretPositionProperty.bind(viewModel.caretPositionProperty());
promptNode.visibleProperty().bind(promptVisibleBinding);
promptNode.fontProperty().bind(promptFontBinding);
} else {
// unbind control properties from viewModel properties, to avoid forwarding
// the internal changes of the latter, while it performs an action
getSkinnable().textLengthProperty.unbind();
getSkinnable().modifiedProperty.unbind();
getSkinnable().selectionProperty.unbind();
getSkinnable().decorationAtCaret.unbind();
getSkinnable().decorationAtParagraph.unbind();
caretPositionProperty.unbind();
promptNode.visibleProperty().unbind();
promptNode.fontProperty().unbind();
}
}
};

private final ChangeListener<Document> documentChangeListener = (obs, ov, nv) -> {
if (!attachedProperty.get()) {
return;
}
if (ov == null && nv != null) {
// new/open
dispose();
Expand All @@ -373,11 +423,26 @@ protected void invalidated() {
}
};

private ObjectBinding<Font> promptFontBinding;
final ObjectProperty<Point2D> caretOriginProperty = new SimpleObjectProperty<>(this, "caretOrigin", DEFAULT_POINT_2D);

private final ObjectBinding<Font> promptFontBinding = Bindings.createObjectBinding(this::getPromptNodeFont,
viewModel.decorationAtCaretProperty(), viewModel.decorationAtParagraphProperty());
private BooleanBinding promptVisibleBinding;

private final ChangeListener<Number> caretChangeListener;
private final ChangeListener<Number> internalCaretChangeListener;
private final IntegerProperty caretPositionProperty = new SimpleIntegerProperty() {
@Override
protected void invalidated() {
int caret = get();
int externalCaret = caret;
if (caret > -1) {
String text = viewModel.getTextBuffer().getText(0, caret);
externalCaret = text.length();
}
getSkinnable().caretPosition.set(externalCaret);
viewModel.getParagraphWithCaret()
.ifPresent(paragraph -> Platform.runLater(paragraphListView::scrollIfNeeded));
}
};

private final InvalidationListener focusListener;
private final EventHandler<DragEvent> dndHandler = this::dndListener;
Expand All @@ -387,8 +452,6 @@ protected void invalidated() {

private final ResourceBundle resources;

final ObjectProperty<Point2D> caretOriginProperty = new SimpleObjectProperty<>(this, "caretOrigin", DEFAULT_POINT_2D);

private class RichVirtualFlow extends VirtualFlow<ListCell<Paragraph>> {

RichVirtualFlow(RichTextArea control) {
Expand Down Expand Up @@ -600,17 +663,6 @@ protected RichTextAreaSkin(final RichTextArea control) {

tableAllowedListener = (obs, ov, nv) -> viewModel.setTableAllowed(nv);
skinToneChangeListener = (obs, ov, nv) -> refreshTextFlow();
caretChangeListener = (obs, ov, nv) -> viewModel.getParagraphWithCaret()
.ifPresent(paragraph -> Platform.runLater(paragraphListView::scrollIfNeeded));
internalCaretChangeListener = (obs, ov, nv) -> {
int caret = nv.intValue();
int externalCaret = caret;
if (caret > -1) {
String text = viewModel.getTextBuffer().getText(0, caret);
externalCaret = text.length();
}
getSkinnable().caretPosition.set(externalCaret);
};

focusListener = o -> paragraphListView.updateLayout();

Expand All @@ -628,6 +680,8 @@ protected RichTextAreaSkin(final RichTextArea control) {
// set prompt text
promptNode = new Text();
setupPromptNode();

viewModel.attachedProperty().subscribe((b0, b) -> attachedProperty.set(b));
setup(control.getDocument());
}

Expand All @@ -639,23 +693,14 @@ protected RichTextAreaSkin(final RichTextArea control) {
@Override
public void dispose() {
viewModel.clearSelection();
viewModel.caretPositionProperty().removeListener(caretChangeListener);
viewModel.caretPositionProperty().removeListener(internalCaretChangeListener);
viewModel.removeChangeListener(textChangeListener);
viewModel.documentProperty().removeListener(documentChangeListener);
viewModel.autoSaveProperty().unbind();
lastValidCaretPosition = -1;
promptNode.textProperty().unbind();
promptNode.fillProperty().unbind();
promptNode.visibleProperty().unbind();
promptNode.fontProperty().unbind();
getSkinnable().editableProperty().removeListener(this::editableChangeListener);
getSkinnable().tableAllowedProperty().removeListener(tableAllowedListener);
getSkinnable().textLengthProperty.unbind();
getSkinnable().modifiedProperty.unbind();
getSkinnable().selectionProperty.unbind();
getSkinnable().decorationAtCaret.unbind();
getSkinnable().decorationAtParagraph.unbind();
getSkinnable().setOnKeyPressed(null);
getSkinnable().setOnKeyTyped(null);
getSkinnable().widthProperty().removeListener(controlPrefWidthListener);
Expand All @@ -667,6 +712,7 @@ public void dispose() {
tableContextMenuItems = null;
editableContextMenuItems = null;
nonEditableContextMenuItems = null;
attachedProperty.set(false);
}

public RichTextAreaViewModel getViewModel() {
Expand All @@ -687,8 +733,7 @@ private void setup(Document document) {
if (document == null) {
return;
}
viewModel.caretPositionProperty().addListener(caretChangeListener);
viewModel.caretPositionProperty().addListener(internalCaretChangeListener);
attachedProperty.set(false);
viewModel.setTextBuffer(new PieceTable(document));
lastValidCaretPosition = viewModel.getTextBuffer().getInternalPosition(document.getCaretPosition());
viewModel.setCaretPosition(lastValidCaretPosition);
Expand All @@ -699,30 +744,6 @@ private void setup(Document document) {
viewModel.autoSaveProperty().bind(getSkinnable().autoSaveProperty());
promptNode.textProperty().bind(getSkinnable().promptTextProperty());
promptNode.fillProperty().bind(promptTextFillProperty());
if (promptVisibleBinding == null) {
promptVisibleBinding = Bindings.createBooleanBinding(
() -> {
Point2D point2D = caretOriginProperty.get();
boolean visible = viewModel.getTextLength() == 0 && viewModel.getCaretPosition() == 0 &&
point2D.getX() > DEFAULT_POINT_2D.getX() && point2D.getY() > DEFAULT_POINT_2D.getY();
if (visible) {
updatePromptNodeLocation();
}
return visible;
},
viewModel.caretPositionProperty(), viewModel.textLengthProperty(), caretOriginProperty);
}
promptNode.visibleProperty().bind(promptVisibleBinding);
if (promptFontBinding == null) {
promptFontBinding = Bindings.createObjectBinding(this::getPromptNodeFont,
viewModel.decorationAtCaretProperty(), viewModel.decorationAtParagraphProperty());
}
promptNode.fontProperty().bind(promptFontBinding);
getSkinnable().textLengthProperty.bind(viewModel.textLengthProperty());
getSkinnable().modifiedProperty.bind(viewModel.savedProperty().not());
getSkinnable().selectionProperty.bind(viewModel.selectionProperty());
getSkinnable().decorationAtCaret.bind(viewModel.decorationAtCaretProperty());
getSkinnable().decorationAtParagraph.bind(viewModel.decorationAtParagraphProperty());
getSkinnable().setOnContextMenuRequested(contextMenuEventEventHandler);
getSkinnable().editableProperty().addListener(this::editableChangeListener);
getSkinnable().tableAllowedProperty().addListener(tableAllowedListener);
Expand All @@ -736,6 +757,7 @@ private void setup(Document document) {
refreshTextFlow();
requestLayout();
editableChangeListener(null); // sets up all related listeners
attachedProperty.set(true);
}

private void setupPromptNode() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Gluon
* Copyright (c) 2022, 2024, Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -32,22 +32,30 @@ public abstract class AbstractCommand<T> {
protected abstract void doUndo(T context);
protected abstract void doRedo(T context);

protected void storeContext(T context){}
protected void restoreContext(T context){}
protected void storeContext(T context) {}
protected void restoreContext(T context) {}
protected void attachContext(T context) {}
protected void detachContext(T context) {}

public final void execute(T context) {
detachContext(context);
storeContext(context);
doRedo(context);
attachContext(context);
}

public final void undo(T context) {
detachContext(context);
doUndo(context);
restoreContext(context);
attachContext(context);
}

public final void redo(T context) {
detachContext(context);
restoreContext(context);
doRedo(context);
attachContext(context);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Gluon
* Copyright (c) 2022, 2024, Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -54,6 +54,16 @@ protected void restoreContext( RichTextAreaViewModel viewModel ) {
viewModel.setSelection(selection);
}

@Override
protected void attachContext(RichTextAreaViewModel viewModel) {
Objects.requireNonNull(viewModel).attach();
}

@Override
protected void detachContext(RichTextAreaViewModel viewModel) {
Objects.requireNonNull(viewModel).detach();
}

@Override
public String toString() {
return getClass().getSimpleName() + " { " +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2023, Gluon
* Copyright (c) 2022, 2024, Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -55,7 +55,6 @@
import javafx.scene.image.Image;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;

import java.text.BreakIterator;
import java.util.ArrayList;
Expand Down Expand Up @@ -307,6 +306,20 @@ public final boolean isSaved() {
return savedProperty.get();
}

// attachedProperty
private final ReadOnlyBooleanWrapper attachedProperty = new ReadOnlyBooleanWrapper(this, "attached", true);
public final ReadOnlyBooleanProperty attachedProperty() {
return attachedProperty.getReadOnlyProperty();
}

final void attach() {
attachedProperty.set(true);
}

final void detach() {
attachedProperty.set(false);
}

public RichTextAreaViewModel(BiFunction<Double, Boolean, Integer> getNextRowPosition, Function<Boolean, Integer> getNextTableCellPosition) {
this.getNextRowPosition = Objects.requireNonNull(getNextRowPosition);
this.getNextTableCellPosition = Objects.requireNonNull(getNextTableCellPosition);
Expand Down
Loading
Loading