/**
* JfxBrowser.java
* Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.expeditee.items.widgets;
/*
* JavaFX is not on the default java classpath until Java 8 (but is still included with Java 7), so your IDE will probably complain that the imports below can't be resolved.
* In Eclipse hitting'Proceed' when told 'Errors exist in project' should allow you to run Expeditee without any issues (although the JFX Browser widget will not display),
* or you can just exclude JfxBrowser, WebParser and JfxbrowserActions from the build path.
*
* If you are using Ant to build/run, 'ant build' will try to build with JavaFX jar added to the classpath.
* If this fails, 'ant build-nojfx' will build with the JfxBrowser, WebParser and JfxbrowserActions excluded from the build path.
*/
import java.awt.Point;
import java.awt.event.KeyListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javafx.animation.FadeTransition;
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.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebEvent;
import javafx.scene.web.WebView;
import javafx.util.Duration;
import netscape.javascript.JSObject;
import org.expeditee.gio.gesture.StandardGestureActions;
import org.expeditee.gio.swing.SwingConversions;
import org.expeditee.gui.DisplayController;
import org.expeditee.gui.FreeItems;
import org.expeditee.gui.MessageBay;
import org.expeditee.io.WebParser;
import org.expeditee.items.Item;
import org.expeditee.items.Picture;
import org.expeditee.items.Text;
import org.expeditee.settings.network.NetworkSettings;
import org.w3c.dom.NodeList;
import javafx.scene.control.skin.TextFieldSkin;
import javafx.scene.text.HitInfo;
/**
* Web browser using a JavaFX WebView.
*
* @author ngw8
* @author jts21
*/
/**
* @author ngw8
*
*/
public class JfxBrowser extends DataFrameWidget {
public static boolean JFXBROWSER_IN_USE = false;
private static final String BACK = "back";
private static final String FORWARD = "forward";
private static final String REFRESH = "refresh";
private static final String CONVERT = "convert";
private static final String VIDEO = "video";
private JFXPanel _panel;
private WebView _webView;
private WebEngine _webEngine;
private Button _forwardButton;
private Button _backButton;
private Button _stopButton;
private Button _goButton;
private Button _convertButton;
private ToggleButton _readableModeButton;
private Label _statusLabel;
private FadeTransition _statusFadeIn;
private FadeTransition _statusFadeOut;
private TextField _urlField;
private ProgressBar _urlProgressBar;
private StackPane _overlay;
private boolean _parserRunning;
private MouseButton _buttonDownId = MouseButton.NONE;
private MouseEvent _backupEvent = null;
private static Field MouseEvent_x, MouseEvent_y;
static {
Platform.setImplicitExit(false);
Font.loadFont(ClassLoader.getSystemResourceAsStream("org/expeditee/assets/resources/fonts/FontAwesome/fontawesome-webfont.ttf"), 12);
try {
MouseEvent_x = MouseEvent.class.getDeclaredField("x");
MouseEvent_x.setAccessible(true);
MouseEvent_y = MouseEvent.class.getDeclaredField("y");
MouseEvent_y.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
public JfxBrowser(Text source, final String[] args) {
// Initial page is either the page stored in the arguments (if there is one stored) or the homepage
super(source, new JFXPanel(), -1, 500, -1, -1, 300, -1);
_panel = (JFXPanel) _swingComponent;
// Quick & easy way of having a cancel function for the web parser.
// Can't just have a JFX button, as the JFX thread is occupied with running JavaScript so it wouldn't receive the click event straight away
_swingComponent.addKeyListener(new KeyListener() {
@Override
public void keyReleased(java.awt.event.KeyEvent e) {
if(e.getKeyCode() == java.awt.event.KeyEvent.VK_ESCAPE) {
JfxBrowser.this.cancel();
}
}
@Override
public void keyPressed(java.awt.event.KeyEvent e) {
}
@Override
public void keyTyped(java.awt.event.KeyEvent e) {
}
});
Platform.runLater(new Runnable() {
@Override
public void run() {
initFx((args != null && args.length > 0) ? args[0] : NetworkSettings.HomePage.get());
}
});
JFXBROWSER_IN_USE = true;
}
/**
* Sets up the browser frame. JFX requires this to be run on a new thread.
*
* @param url
* The URL to be loaded when the browser is created
*/
private void initFx(String url) {
try {
StackPane mainLayout = new StackPane();
mainLayout.setId("jfxbrowser");
VBox vertical = new VBox();
HBox horizontal = new HBox();
horizontal.getStyleClass().add("custom-toolbar");
this._backButton = new Button("\uf060");
this._backButton.setTooltip(new Tooltip("Back"));
this._backButton.setMinWidth(Button.USE_PREF_SIZE);
this._backButton.setMaxHeight(Double.MAX_VALUE);
this._backButton.setFocusTraversable(false);
this._backButton.getStyleClass().addAll("first", "fa");
this._backButton.setDisable(true);
this._forwardButton = new Button("\uf061");
this._forwardButton.setTooltip(new Tooltip("Forward"));
this._forwardButton.setMinWidth(Button.USE_PREF_SIZE);
this._forwardButton.setMaxHeight(Double.MAX_VALUE);
this._forwardButton.setFocusTraversable(false);
this._forwardButton.getStyleClass().addAll("last", "fa");
this._urlField = new TextField(url);
this._urlField.getStyleClass().add("url-field");
this._urlField.setMaxWidth(Double.MAX_VALUE);
this._urlField.setFocusTraversable(false);
this._stopButton = new Button("\uF00D");
this._stopButton.setTooltip(new Tooltip("Stop loading the page"));
this._stopButton.getStyleClass().addAll("url-button", "url-cancel-button", "fa");
this._stopButton.setMinWidth(Button.USE_PREF_SIZE);
this._stopButton.setMaxHeight(Double.MAX_VALUE);
StackPane.setAlignment(this._stopButton, Pos.CENTER_RIGHT);
this._stopButton.setFocusTraversable(false);
this._goButton = new Button("\uf061");
this._goButton.setTooltip(new Tooltip("Load the entered address"));
this._goButton.getStyleClass().addAll("url-button", "url-go-button", "fa");
this._goButton.setMinWidth(Button.USE_PREF_SIZE);
this._goButton.setMaxHeight(Double.MAX_VALUE);
StackPane.setAlignment(this._goButton, Pos.CENTER_RIGHT);
this._goButton.setFocusTraversable(false);
this._readableModeButton = new ToggleButton();
this._readableModeButton.setMinWidth(Button.USE_PREF_SIZE);
this._readableModeButton.setFocusTraversable(false);
this._readableModeButton.setTooltip(new Tooltip("Switch to an easy-to-read view of the page"));
Image readableModeIcon = new Image(ClassLoader.getSystemResourceAsStream("org/expeditee/assets/images/readableModeIcon.png"));
this._readableModeButton.setGraphic(new ImageView(readableModeIcon));
this._convertButton = new Button("Convert");
this._convertButton.setMinWidth(Button.USE_PREF_SIZE);
this._convertButton.setFocusTraversable(false);
this._urlProgressBar = new ProgressBar();
this._urlProgressBar.getStyleClass().add("url-progress-bar");
this._urlProgressBar.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
// Status label that displays the URL when a link is hovered over
this._statusLabel = new Label();
this._statusLabel.getStyleClass().addAll("browser-status-label");
this._statusLabel.setVisible(false);
this._statusFadeIn = new FadeTransition();
this._statusFadeIn.setDuration(Duration.millis(200));
this._statusFadeIn.setNode(this._statusLabel);
this._statusFadeIn.setFromValue(0);
this._statusFadeIn.setToValue(1);
this._statusFadeIn.setCycleCount(1);
this._statusFadeIn.setAutoReverse(false);
this._statusFadeOut = new FadeTransition();
this._statusFadeOut.setDuration(Duration.millis(400));
this._statusFadeOut.setNode(this._statusLabel);
this._statusFadeOut.setFromValue(1);
this._statusFadeOut.setToValue(0);
this._statusFadeOut.setCycleCount(1);
this._statusFadeOut.setAutoReverse(false);
this._statusFadeOut.setOnFinished(new EventHandler() {
@Override
public void handle(ActionEvent arg0) {
JfxBrowser.this._statusLabel.setVisible(false);
}
});
StackPane urlbar = new StackPane();
urlbar.getChildren().addAll(_urlProgressBar, this._urlField, this._stopButton, this._goButton);
horizontal.getChildren().addAll(this._backButton, this._forwardButton, urlbar, this._readableModeButton, this._convertButton);
HBox.setHgrow(this._backButton, Priority.NEVER);
HBox.setHgrow(this._forwardButton, Priority.NEVER);
HBox.setHgrow(this._convertButton, Priority.NEVER);
HBox.setHgrow(this._readableModeButton, Priority.NEVER);
HBox.setHgrow(urlbar, Priority.ALWAYS);
HBox.setMargin(this._readableModeButton, new Insets(0, 5, 0, 5));
HBox.setMargin(this._forwardButton, new Insets(0, 5, 0, 0));
this._webView = new WebView();
this._webView.setMaxWidth(Double.MAX_VALUE);
this._webView.setMaxHeight(Double.MAX_VALUE);
this._webEngine = this._webView.getEngine();
this._urlProgressBar.progressProperty().bind(_webEngine.getLoadWorker().progressProperty());
// Pane to hold just the webview. This seems to be the only way to allow the webview to be resized to greater than its parent's
// size. This also means that the webview's prefSize must be manually set when the Pane resizes, using the event handlers below
Pane browserPane = new Pane();
browserPane.getChildren().addAll(_webView, this._statusLabel);
HBox.setHgrow(browserPane, Priority.ALWAYS);
VBox.setVgrow(browserPane, Priority.ALWAYS);
browserPane.widthProperty().addListener(new ChangeListener