Marcel
Marcel

Reputation: 1594

JavaFX HTMLEditor Hyperlinks

I am trying to make kind of a Chat with formatted input, my problem is that you are not able to put links into the JavaFX HTMLEditor by default, so i have added a button that replaces the selected text with a hyperlink. My problem is that 1: the hyperlink in the editor is clickable and will cause the editor to open the link if you click it and problem number 2: when i click the link in the webView it doesn't open in the external browser but inside of the webView itself, so it practially is the same Problem since the HTMLEditor is using a webView. Does anyone know how to "fix" that?

Upvotes: 1

Views: 1332

Answers (1)

vl4d1m1r4
vl4d1m1r4

Reputation: 1791

Since the WebView in JavaFX uses java.net.URLConnection underneath you can use its built in mechanisms to provide your custom handler that will create a connection which delegates the url to the OS that will open the url in the default browser. Here is an example:

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

import javafx.application.Application;
import javafx.application.HostServices;
import javafx.scene.Scene;
import javafx.scene.web.HTMLEditor;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

public class HTMLEditorSample extends Application {

  @Override
  public void start(Stage stage) {

    stage.setTitle("HTMLEditor Sample");
    stage.setWidth(400);
    stage.setHeight(300);
    final HTMLEditor htmlEditor = new HTMLEditor();

    htmlEditor.setPrefHeight(245);
    Scene scene = new Scene(htmlEditor);
    stage.setScene(scene);
    stage.show();

    URL.setURLStreamHandlerFactory(protocol -> {

      if (protocol.startsWith("http")) {
        return new CustomUrlHandler();
      }

      return null;
    });

    WebView webview = (WebView) htmlEditor.lookup(".web-view");
    webview.getEngine().load("http://google.com");

  }

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

  public class CustomUrlHandler extends URLStreamHandler {

    @Override
    protected URLConnection openConnection(URL u) throws IOException {
      return new HostServicesUrlConnection(u, getHostServices());
    }
  }

  public class HostServicesUrlConnection extends HttpURLConnection {

    private URL urlToOpen;
    private HostServices hostServices;

    protected HostServicesUrlConnection(URL u, HostServices hostServices) {
      super(u);
      this.urlToOpen= u;
      this.hostServices = hostServices;
    }

    @Override
    public void disconnect() {
      // do nothing
    }

    @Override
    public boolean usingProxy() {
      return false;
    }

    @Override
    public void connect() throws IOException {
      hostServices.showDocument(urlToOpen.toExternalForm());
    }

    @Override
    public InputStream getInputStream() throws IOException {
      return new InputStream() {

        @Override
        public int read() throws IOException {
          return 0;
        }
      };
    }

  }
}

UPDATE:

The previous solution will override the functionality for every other class that uses URLConnection which might not be what you want. I found an easier solution to the problem by playing with the location and state of the load worker. Note that the canceling of load worker without the Platform.runLater crashed the JVM on JDK version 8u66:

webview.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {

  @Override
  public void changed(ObservableValue<? extends State> observable, State oldValue, State newValue) {
    Platform.runLater(() -> {
      webview.getEngine().getLoadWorker().cancel();
    });
  }
});

webview.getEngine().locationProperty().addListener(new ChangeListener<String>() {

  @Override
  public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
    getHostServices().showDocument(newValue);
  }
});

Upvotes: 1

Related Questions