package info.opencards.md;
import com.sun.javafx.application.PlatformImpl;
import info.opencards.OpenCards;
import info.opencards.Utils;
import info.opencards.core.*;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.html.HTMLAnchorElement;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
/**
* Nice general oveview about how to integrate javafx into swing app
* http://what-when-how.com/javafx-2/embedding-javafx-scenes-in-swing-and-swt-applications-collections-and-concurrency/
* <p>
* Automatix rescaling?
* http://java-no-makanaikata.blogspot.de/2012/10/javafx-webview-size-trick.html
*/
public class MdSlideManager extends AbstractSlideManager {
private WebEngine webEngine;
private JFXPanel jfxPanel;
public List<MarkdownFlashcard> slides;
@Override
public boolean showCardQuestion(Item item) {
// todo implemnt optional sync
if (isItemOutOfSync(item)) return false;
int cardIndex = item.getFlashCard().getCardIndex();
ReversePolicy cardRevPolicy = item.getFlashCard().getTodaysRevPolicy();
// note:
// The random-reverse mode is encoded directly in the flashcard in order to make its policy temporary
// persistent during the todaysltm-sessions
MarkdownFlashcard curSlide = slides.get(cardIndex - 1);
switch (cardRevPolicy) {
case NORMAL:
renderHtml(curSlide.getQuestion());
break;
case REVERSE:
renderHtml(curSlide.getAnswer());
break;
default:
throw new RuntimeException("unsupported reverse policy");
}
return true;
}
/**
* Test if item is in sync with given slide-show instance
*/
private boolean isItemOutOfSync(Item item) {
final int itemCardIndex = item.getFlashCard().getCardIndex();
return itemCardIndex < 0 || slides.size() < itemCardIndex - 1 || slides.isEmpty() || slides.get(itemCardIndex - 1).getQuestion().hashCode() != item.getFlashCard().getCardID();
}
private void renderHtml(String content) {
// http://stackoverflow.com/questions/21083945/how-to-avoid-not-on-fx-application-thread-currentthread-javafx-application-th
Platform.runLater(() -> {
webEngine.loadContent("<body>" + content + "</body≈>");
redirectLinksToBrowser(webEngine);
}
);
}
// http://www.java2s.com/Code/Java/JavaFX/WebEngineLoadListener.htm
// http://stackoverflow.com/questions/15555510/javafx-stop-opening-url-in-webview-open-in-browser-instead
private static void redirectLinksToBrowser(WebEngine webEngine) {
webEngine.getLoadWorker().stateProperty().addListener(
(ov, oldState, newState) -> {
// adjust link handling
if (webEngine.getDocument() == null)
return;
NodeList nodeList = webEngine.getDocument().getElementsByTagName("a");
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
EventTarget eventTarget = (EventTarget) node;
eventTarget.addEventListener("click", evt -> {
EventTarget target = evt.getCurrentTarget();
HTMLAnchorElement anchorElement = (HTMLAnchorElement) target;
String href = anchorElement.getHref();
//handle opening URL outside JavaFX WebView
try {
Desktop.getDesktop().browse(new URI(href));
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
}
evt.preventDefault();
}, false);
}
});
}
@Override
public boolean showCompleteCard(Item item) {
MarkdownFlashcard curSlide = slides.get(item.getFlashCard().getCardIndex() - 1);
renderHtml(curSlide.getQuestion() + "\n" + curSlide.getAnswer());
return true;
}
@Override
public void startFileSession(ItemCollection cardItemCollection) {
}
@Override
public void openCardFile(CardFile cardFile) {
cardFile.synchronize();
slides = MarkdownParserKt.parseMD(cardFile.getFileLocation(), cardFile.getProperties().useMarkdownSelector());
jfxPanel = new JFXPanel();
createScene();
JPanel renderContainer = OpenCards.getInstance().getLearnPanel().getSlideRenderPanel();
renderContainer.removeAll();
renderContainer.add(jfxPanel);
renderContainer.validate();
renderContainer.repaint();
renderContainer.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
jfxPanel.setPreferredSize(renderContainer.getPreferredSize());
jfxPanel.repaint();
}
@Override
public void componentShown(ComponentEvent e) {
jfxPanel.repaint();
}
});
curCardFile = cardFile;
OpenCards.getInstance().setTitle("OpenCards: " + cardFile.getFileLocation().getName());
}
@Override
public void stopFileSession() {
// Utils.log("stopped md-file session");
slides = null;
}
private void createScene() {
PlatformImpl.startup(() -> {
Stage stage;
WebView browser;
stage = new Stage();
stage.setTitle("Hello Java FX");
stage.setResizable(true);
Group root = new Group();
Scene scene = new Scene(root, 80, 20);
stage.setScene(scene);
// Set up the embedded browser:
browser = new WebView();
webEngine = browser.getEngine();
// webEngine.load("http://heise.de");
// ScrollPane scrollPane = new ScrollPane();
// scrollPane.setContent(browser);
webEngine.loadContent("<b>asdf</b>");
// root.getChildren().addAll(scrollPane);
// scene.setRoot(root);
// stage.setScene(scene);
root.getChildren().add(browser);
// children.add(browser);
jfxPanel.setScene(scene);
});
while (webEngine == null) {
Utils.sleep(25);
}
}
}