package org.geogebra.desktop.gui.dialog;
import java.awt.Dimension;
import java.awt.Point;
import java.net.URI;
import java.net.URISyntaxException;
import javax.swing.JDialog;
import org.geogebra.common.util.debug.Log;
import org.geogebra.desktop.main.AppD;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker.State;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
/**
* Provides basic functionality for creating a JavaFX WebView within a dialog.
* The method <code>createWebView(String startURL)</code> has to be called to
* create and initialize the webView
*
* @author stefan
*
*/
public abstract class WebViewDialog extends JDialog {
private static final long serialVersionUID = 1L;
private static final String EVENT_TYPE_CLICK = "click";
protected JFXPanel fxPanel;
/** Reference to the application */
protected AppD app;
/**
* The webview in this dialog. is created by the method
* <code>createWebView</code>.
*/
protected WebView webView;
/**
* Sets the main window of the GeoGebra App as owner of the dialog
*
* @param app
* The App
* @param modal
* Specifies whether dialog blocks user input to other top-level
* windows when shown. If <code>true</code>, the modality type
* property is set to <code>DEFAULT_MODALITY_TYPE</code>,
* otherwise the dialog is modeless.
*/
public WebViewDialog(AppD app, boolean modal) {
super(app.getFrame(), modal);
this.app = app;
}
/**
* Creates and initializes the webview. This method has to be called from
* implementing dialogs to create the WebView.
*
* @param startURL
* The initial URL that will be loaded in the webview
* @return The JFXPanel that is the parent of the WebView.
*/
protected JFXPanel createWebView(final String startURL) {
// Create the JavaFX Panel for the WebView
fxPanel = new JFXPanel();
fxPanel.setLocation(new Point(0, 0));
// Initialize the webView in a JavaFX-Thread
Platform.runLater(new Runnable() {
@Override
public void run() {
initWebView(fxPanel, startURL);
}
});
app.setComponentOrientation(this);
return fxPanel;
}
/**
* Creates a web view and opens the login page of GeoGebraTube
*
* @param fxPanel
* The panel that should hold the web view
* @param startURL
* The Start URL that is loaded in the WebView
*/
void initWebView(final JFXPanel fxPanel, String startURL) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root);
fxPanel.setScene(scene);
webView = new WebView();
// Listen for successful page load to query the login result
webView.getEngine().getLoadWorker().stateProperty()
.addListener(new ChangeListener<State>() {
@Override
public void changed(ObservableValue<? extends State> ov,
State oldState, State newState) {
if (newState == State.SUCCEEDED) {
Document doc = getWebEngine().getDocument();
if (doc != null) {
Log.debug("Load page finished: "
+ doc.getBaseURI());
}
onPageLoaded();
}
}
});
// Load the login page
webView.getEngine().load(startURL);
root.setCenter(webView);
}
/**
* Returns the javaFX webEngine instance that is associated with this web
* view.
*
* @return The instance of WebEngine or null, if the web view was not
* created yet.
*/
public WebEngine getWebEngine() {
if (webView == null) {
return null;
}
return webView.getEngine();
}
/**
* This method is called after a page was loaded in the WebView and can be
* overwritten to handle this event.
*/
protected void onPageLoaded() {
// No default action
}
/**
* Can be overwritten to handle hyperlink clicks. This handler is only
* called, when <code>addHyperlinkListener()</code> was called.
*
* @param href
* The URL of the clicked hyperlink
* @param absoluteURL
* If href is a relative URL this is the absolute URL. Otherwise
* this is the same as href.
* @param domainName
* The domainname of the clicked link.
* @param ev
* The web event that was triggered. Can be used to gather more
* detailed information about the clicked element or to prevent
* the default action by calling <code>ev.preventDefault()</code>
* .
*/
void onHyperlinkClicked(String href, String absoluteURL, String domainName,
Event ev) {
// No Default action
}
/**
* Resizes the dialog to fit the size of the document in the webview
*/
protected void setDialogSizeToPageSize() {
String widthScript = "Math.max( document.documentElement.clientWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth );";
final int width = ((Integer) getWebEngine().executeScript(widthScript))
.intValue();
String heightScript = "Math.max( document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight );";
final int height = ((Integer) getWebEngine()
.executeScript(heightScript)).intValue();
setPreferredSize(new Dimension(
width + (getWidth() - getContentPane().getWidth()),
height + (getHeight() - getContentPane().getHeight())));
pack();
setLocationRelativeTo(app.getFrame());
}
/**
* Adds a listener for clicks on hyperlinks, if not added yet. This method
* has to be called from <code>onPageLoaded</code>.
*
* Calls the method <code>onLinkClicked(String href)</code> when a link was
* clicked
*/
protected void addHyperlinkListener() {
// Some links need to be opened in an external browser. Add a listener
// to handle this clicks.
EventListener listener = new EventListener() {
@Override
public void handleEvent(Event ev) {
String domEventType = ev.getType();
Log.debug("EventType: " + domEventType);
if (domEventType.equals(EVENT_TYPE_CLICK)) {
String href = ((Element) ev.getTarget())
.getAttribute("href");
if (href != null) {
String absoluteURL = href;
String domainName;
// Make relative urls absolute
if (!href.startsWith("http://")
&& !href.startsWith("https://")) {
domainName = (String) getWebEngine()
.executeScript("window.location.origin");
absoluteURL = domainName + href;
} else {
domainName = getDomainNameFromURL(absoluteURL);
}
WebViewDialog.this.onHyperlinkClicked(href, absoluteURL,
domainName, ev);
}
}
}
};
Document doc = getWebEngine().getDocument();
if (doc != null) {
NodeList nodeList = doc.getElementsByTagName("a");
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof EventTarget) {
((EventTarget) node).addEventListener(EVENT_TYPE_CLICK,
listener, false);
}
}
nodeList = doc.getElementsByTagName("input");
for (int i = 0; i < nodeList.getLength(); i++) {
if (nodeList.item(i).getAttributes()
.getNamedItem("href") != null) {
((EventTarget) nodeList.item(i)).addEventListener(
EVENT_TYPE_CLICK, listener, false);
}
}
}
}
/**
* Extracts the domain name form an URL
*
* @param url
* The URL
* @return The domainname
*/
public static String getDomainNameFromURL(String url) {
String domain;
try {
URI uri = new URI(url);
domain = uri.getHost();
} catch (URISyntaxException e) {
domain = null;
}
return domain;
}
}