Skip to content

Commit

Permalink
Merge pull request #550 from JordanMartinez/hyperlinkDemo
Browse files Browse the repository at this point in the history
Add Hyperlink demo - minimum needed for custom object integration
  • Loading branch information
JordanMartinez authored Aug 11, 2017
2 parents b5a167d + 116a661 commit 99770e5
Show file tree
Hide file tree
Showing 6 changed files with 579 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.fxmisc.richtext.demo.hyperlink;

public class Hyperlink<S> {

private final String originalDisplayedText;
private final String displayedText;
private final S style;
private final String link;

Hyperlink(String originalDisplayedText, String displayedText, S style, String link) {
this.originalDisplayedText = originalDisplayedText;
this.displayedText = displayedText;
this.style = style;
this.link = link;
}

public boolean isEmpty() {
return length() == 0;
}

public boolean isReal() {
return length() > 0;
}

public boolean shareSameAncestor(Hyperlink<S> other) {
return link.equals(other.link) && originalDisplayedText.equals(other.originalDisplayedText);
}

public int length() {
return displayedText.length();
}

public char charAt(int index) {
return isEmpty() ? '\0' : displayedText.charAt(index);
}

public String getOriginalDisplayedText() { return originalDisplayedText; }

public String getDisplayedText() {
return displayedText;
}

public String getLink() {
return link;
}

public Hyperlink<S> subSequence(int start, int end) {
return new Hyperlink<>(originalDisplayedText, displayedText.substring(start, end), style, link);
}

public Hyperlink<S> subSequence(int start) {
return new Hyperlink<>(originalDisplayedText, displayedText.substring(start), style, link);
}

public S getStyle() {
return style;
}

public Hyperlink<S> setStyle(S style) {
return new Hyperlink<>(originalDisplayedText, displayedText, style, link);
}

public Hyperlink<S> mapDisplayedText(String text) {
return new Hyperlink<>(originalDisplayedText, text, style, link);
}

@Override
public String toString() {
return isEmpty()
? String.format("EmptyHyperlink[original=%s style=%s link=%s]", originalDisplayedText, style, link)
: String.format("RealHyperlink[original=%s displayedText=%s, style=%s, link=%s]",
originalDisplayedText, displayedText, style, link);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.fxmisc.richtext.demo.hyperlink;

import com.sun.deploy.uitoolkit.impl.fx.HostServicesFactory;
import com.sun.javafx.application.HostServicesDelegate;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.fxmisc.flowless.VirtualizedScrollPane;

import java.util.function.Consumer;

/**
* Demonstrates the minimum needed to support custom objects (in this case, hyperlinks) alongside of text.
*
* Note: demo does not handle cases where the link changes its state when it has already been visited
*/
public class HyperlinkDemo extends Application {

public static void main(String[] args) {
launch(args);
}

@Override
public void start(Stage primaryStage) {
Consumer<String> showLink = HostServicesFactory.getInstance(this)::showDocument;
TextHyperlinkArea area = new TextHyperlinkArea(showLink);

area.appendText("Some text in the area\n");
area.appendWithLink("Google.com", "http://www.google.com");

VirtualizedScrollPane<TextHyperlinkArea> vsPane = new VirtualizedScrollPane<>(area);

Scene scene = new Scene(vsPane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.fxmisc.richtext.demo.hyperlink;

import org.fxmisc.richtext.model.SegmentOps;

import java.util.Optional;

public class HyperlinkOps<S> implements SegmentOps<Hyperlink<S>, S> {

@Override
public int length(Hyperlink<S> hyperlink) {
return hyperlink.length();
}

@Override
public char charAt(Hyperlink<S> hyperlink, int index) {
return hyperlink.charAt(index);
}

@Override
public String getText(Hyperlink<S> hyperlink) {
return hyperlink.getDisplayedText();
}

@Override
public Hyperlink<S> subSequence(Hyperlink<S> hyperlink, int start, int end) {
return hyperlink.subSequence(start, end);
}

@Override
public Hyperlink<S> subSequence(Hyperlink<S> hyperlink, int start) {
return hyperlink.subSequence(start);
}

@Override
public S getStyle(Hyperlink<S> hyperlink) {
return hyperlink.getStyle();
}

@Override
public Hyperlink<S> setStyle(Hyperlink<S> hyperlink, S style) {
return hyperlink.setStyle(style);
}

@Override
public Optional<Hyperlink<S>> join(Hyperlink<S> currentSeg, Hyperlink<S> nextSeg) {
if (currentSeg.isEmpty()) {
if (nextSeg.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(nextSeg);
}
} else {
if (nextSeg.isEmpty()) {
return Optional.of(currentSeg);
} else {
return concatHyperlinks(currentSeg, nextSeg);
}
}
}

private Optional<Hyperlink<S>> concatHyperlinks(Hyperlink<S> leftSeg, Hyperlink<S> rightSeg) {
if (!leftSeg.shareSameAncestor(rightSeg)) {
return Optional.empty();
}

String original = leftSeg.getOriginalDisplayedText();
String leftText = leftSeg.getDisplayedText();
String rightText = rightSeg.getDisplayedText();
int leftOffset = 0;
int rightOffset = 0;
for (int i = 0; i <= original.length() - leftText.length(); i++) {
if (original.regionMatches(i, leftText, 0, leftText.length())) {
leftOffset = i;
break;
}
}
for (int i = 0; i <= original.length() - rightText.length(); i++) {
if (original.regionMatches(i, rightText, 0, rightText.length())) {
rightOffset = i;
break;
}
}

if (rightOffset + rightText.length() == leftOffset) {
return Optional.of(leftSeg.mapDisplayedText(rightText + leftText));
} else if (leftOffset + leftText.length() == rightOffset) {
return Optional.of(leftSeg.mapDisplayedText(leftText + rightText));
} else {
return Optional.empty();
}
}

@Override
public Hyperlink<S> createEmpty() {
return new Hyperlink<>("", "", null, "");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.fxmisc.richtext.demo.hyperlink;

import javafx.geometry.VPos;
import org.fxmisc.richtext.GenericStyledArea;
import org.fxmisc.richtext.TextExt;
import org.fxmisc.richtext.model.ReadOnlyStyledDocument;
import org.fxmisc.richtext.model.StyledText;
import org.fxmisc.richtext.model.TextOps;
import org.reactfx.util.Either;

import java.util.function.Consumer;

public class TextHyperlinkArea extends GenericStyledArea<Void, Either<StyledText<TextStyle>, Hyperlink<TextStyle>>, TextStyle> {

private static final TextOps<StyledText<TextStyle>, TextStyle> STYLED_TEXT_OPS = StyledText.textOps();
private static final HyperlinkOps<TextStyle> HYPERLINK_OPS = new HyperlinkOps<>();
private static final TextOps<Either<StyledText<TextStyle>, Hyperlink<TextStyle>>, TextStyle> EITHER_OPS = STYLED_TEXT_OPS._or(HYPERLINK_OPS);

public TextHyperlinkArea(Consumer<String> showLink) {
super(
null,
(t, p) -> {},
TextStyle.EMPTY,
EITHER_OPS,
e -> e.unify(
styledText ->
createStyledTextNode(t -> {
t.setText(styledText.getText());
t.setStyle(styledText.getStyle().toCss());
}),
hyperlink ->
createStyledTextNode(t -> {
if (hyperlink.isReal()) {
t.setText(hyperlink.getDisplayedText());
t.getStyleClass().add("hyperlink");
t.setOnMouseClicked(ae -> showLink.accept(hyperlink.getLink()));
}
})
)
);

getStyleClass().add("text-hyperlink-area");
getStylesheets().add(TextHyperlinkArea.class.getResource("text-hyperlink-area.css").toExternalForm());
}

public void appendWithLink(String displayedText, String link) {
replaceWithLink(getLength(), getLength(), displayedText, link);
}

public void replaceWithLink(int start, int end, String displayedText, String link) {
replace(start, end, ReadOnlyStyledDocument.fromSegment(
Either.right(new Hyperlink<>(displayedText, displayedText, TextStyle.EMPTY, link)),
null,
TextStyle.EMPTY,
EITHER_OPS
));
}

public static TextExt createStyledTextNode(Consumer<TextExt> applySegment) {
TextExt t = new TextExt();
t.setTextOrigin(VPos.TOP);
applySegment.accept(t);

// XXX: binding selectionFill to textFill,
// see the note at highlightTextFill
t.impl_selectionFillProperty().bind(t.fillProperty());
return t;
}
}
Loading

0 comments on commit 99770e5

Please sign in to comment.