utybo.branchingstorytree.swing.visuals.NodePanel.java Source code

Java tutorial

Introduction

Here is the source code for utybo.branchingstorytree.swing.visuals.NodePanel.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This Source Code Form is "Incompatible With Secondary Licenses", as
 * defined by the Mozilla Public License, v. 2.0.
 */
package utybo.branchingstorytree.swing.visuals;

import java.awt.BorderLayout;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;

import javax.swing.ImageIcon;
import javax.swing.SwingUtilities;

import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.html.HTMLAnchorElement;

import javafx.application.Platform;
import javafx.concurrent.Worker.State;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import utybo.branchingstorytree.api.BSTException;
import utybo.branchingstorytree.api.Experimental;
import utybo.branchingstorytree.api.StoryUtils;
import utybo.branchingstorytree.api.story.BranchingStory;
import utybo.branchingstorytree.api.story.TextNode;
import utybo.branchingstorytree.swing.Icons;
import utybo.branchingstorytree.swing.Messagers;
import utybo.branchingstorytree.swing.OpenBST;
import utybo.branchingstorytree.swing.OpenBSTGUI;
import utybo.branchingstorytree.swing.VisualsUtils;
import utybo.branchingstorytree.swing.impl.IMGClient;
import utybo.branchingstorytree.swing.utils.Lang;
import utybo.branchingstorytree.swing.utils.MarkupUtils;

public class NodePanel extends JScrollablePanel {
    private static final String FONT_UBUNTU, FONT_LIBRE_BASKERVILLE, STYLE;

    static {
        FONT_UBUNTU = loadFont("ubuntu");
        FONT_LIBRE_BASKERVILLE = loadFont("libre_baskerville");

        String s = null;
        try {
            s = IOUtils.toString(NodePanel.class.getResourceAsStream("/utybo/branchingstorytree/swing/story.css"),
                    StandardCharsets.UTF_8);
        } catch (IOException e) {
            OpenBST.LOG.warn("Failed to load fonts CSS file", e);
            s = "";
        }
        STYLE = s;
    }

    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private static final String defaultFont = "libre_baskerville";
    private final IMGClient imageClient;
    private String text;
    private Color textColor;
    private boolean backgroundVisible = true;
    private WebView view;
    private boolean hrefEnabled;
    private final StoryPanel parent;
    private boolean isDark, isAlreadyBuilt;
    @Experimental
    private ArrayList<String> additionalCSS = new ArrayList<>();

    private final Consumer<Boolean> callback = b -> {
        isDark = b;
        if (isAlreadyBuilt) {
            build();
        }
    };

    public NodePanel(BranchingStory story, StoryPanel parent, final IMGClient imageClient) {
        OpenBSTGUI.getInstance().addDarkModeCallback(callback);
        callback.accept(OpenBSTGUI.getInstance().isDark());
        this.parent = parent;
        this.imageClient = imageClient;
        setLayout(new BorderLayout());

        JFXPanel panel = new JFXPanel();
        add(panel, BorderLayout.CENTER);

        CountDownLatch cdl = new CountDownLatch(1);
        Platform.runLater(() -> {
            try {
                view = new WebView();
                view.getEngine().setOnAlert(e -> SwingUtilities
                        .invokeLater(() -> Messagers.showMessage(OpenBSTGUI.getInstance(), e.getData())));
                view.getEngine().getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
                    if (newState == State.SUCCEEDED) {
                        Document doc = view.getEngine().getDocument();
                        NodeList nl = doc.getElementsByTagName("a");
                        for (int i = 0; i < nl.getLength(); i++) {
                            Node n = nl.item(i);
                            HTMLAnchorElement a = (HTMLAnchorElement) n;
                            if (a.getHref() != null) {
                                ((EventTarget) a).addEventListener("click", ev -> {
                                    if (!hrefEnabled) {
                                        ev.preventDefault();
                                    }
                                }, false);
                            }
                        }
                    }
                });
                Scene sc = new Scene(view);
                try {
                    view.getEngine()
                            .loadContent(IOUtils
                                    .toString(
                                            NodePanel.class.getResourceAsStream(
                                                    "/utybo/branchingstorytree/swing/html/error.html"),
                                            StandardCharsets.UTF_8)
                                    .replace("$MSG", Lang.get("story.problem")).replace("$STYLE", FONT_UBUNTU));
                } catch (IOException e) {
                    OpenBST.LOG.warn("Error on trying to load error HTML file", e);
                }
                panel.setScene(sc);
            } finally {
                cdl.countDown();
            }
        });
        try {
            cdl.await();
        } catch (InterruptedException e) {
            OpenBST.LOG.warn("Synchronization failed", e);
        }
    }

    private static String loadFont(String name) {
        String s;
        try {
            s = IOUtils.toString(
                    new XZCompressorInputStream(NodePanel.class
                            .getResourceAsStream("/utybo/branchingstorytree/swing/font/" + name + ".css.xz")),
                    StandardCharsets.UTF_8);
        } catch (IOException e) {
            OpenBST.LOG.warn("Failed to load fonts CSS file", e);
            s = "";
        }
        return s;
    }

    public void applyNode(final BranchingStory story, final TextNode textNode) throws BSTException {
        final String text = StoryUtils.solveVariables(textNode, story);
        final int markupLanguage = MarkupUtils.solveMarkup(parent.story, story, textNode);
        setText(MarkupUtils.translateMarkup(markupLanguage, text));

        if (textNode.hasTag("color")) {
            final String color = textNode.getTag("color");
            setTextColor(color);
        } else {
            textColor = null;
        }

        if (textNode.hasTag("img_background")) {
            final String bg = textNode.getTag("img_background");
            if ("none".equals(bg)) {
                imageClient.setBackground(null);
            } else {
                imageClient.setBackground(bg);
            }
        } else if (textNode.hasTag("img_manual") && Boolean.parseBoolean(textNode.getTag("img_manual"))) {
            imageClient.setBackground(null);
        }

        build();
    }

    private void build() {
        // Build the base, with fonts and style (injecting font info)
        String base = "<head><meta charset=\"utf-8\"/>" //
                + "<style type='text/css'>" + getCurrentFontCss() + "</style>" //
                + "<style type='text/css'>" + STYLE.replace("$f", getCurrentFont()) + "</style>" //
                + "$CUSTOMCSS" + "</head>" //
                + "<body class='$bbg" + (isDark ? " dark" : "") + "'>" //
                + "<div class='storydiv' style=\"$COLOR\">" + text + "</div></body>";
        String additional, c;

        if (imageClient.getCurrentBackground() != null && backgroundVisible) {
            // Inject background (base64) and add the background class to the body
            base = base.replace("$b64bg", imageClient.getBase64Background()).replace("$bbg", "bg");
            additional = isDark ? "background-color:rgba(0,0,0,0.66)" : "background-color:rgba(255,255,255,0.66)";
        } else {
            base.replace("$bbg", "");
            additional = "";
        }
        if (textColor != null) {
            c = "color: " + MarkupUtils.toHex(textColor.getRed(), textColor.getGreen(), textColor.getBlue());
        } else {
            c = isDark ? "color: #FFFFFF" : "";
        }

        // $EXPERIMENTAL
        StringBuilder sb = new StringBuilder();
        for (String string : additionalCSS) {
            sb.append("<style type='text/css'>" + string + "</style>");
        }

        String s = base.replace("$ADDITIONAL", additional).replace("$COLOR", c)
                // $EXPERIMENTAL
                .replace("$CUSTOMCSS", sb.toString());

        VisualsUtils.invokeJfxAndWait(() -> {
            view.getEngine().loadContent(s);
        });

        isAlreadyBuilt = true;
    }

    private String getCurrentFontCss() {
        String fontName = getCurrentFont().toLowerCase();
        if (fontName.equals("ubuntu"))
            return FONT_UBUNTU;
        else if (fontName.contains("baskerville"))
            return FONT_LIBRE_BASKERVILLE;
        else
            return "";
    }

    private String getCurrentFont() {
        if ((int) parent.currentNode.getStory().getRegistry()
                .get("__nonlatin_" + parent.currentNode.getStory().getTag("__sourcename"), 0) == 1) {
            return "sans-serif";
        }
        return parseFont(parent.currentNode.getStory());
    }

    private String parseFont(BranchingStory s) {
        if (s.hasTag("font")) {
            return s.getTag("font");
        } else if (parent.story.hasTag("font")) {
            return parent.story.getTag("font");
        } else {
            return defaultFont;
        }
    }

    public void setText(final String text) {
        this.text = text;
    }

    public void setTextColor(final String color) {
        Color c = null;
        if (color.startsWith("#")) {
            c = new Color(Integer.parseInt(color.substring(1), 16));
        } else {
            try {
                c = (Color) Color.class.getField(color).get(null);
            } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException
                    | SecurityException e) {
                OpenBST.LOG.warn("Color does not exist : " + color, e);
                SwingUtilities.invokeLater(() -> Messagers.showMessage(OpenBSTGUI.getInstance(),
                        Lang.get("story.unknowncolor").replace("$c", color), Messagers.TYPE_ERROR));
            }
        }
        if (c != null) {
            textColor = c;
        } else {
            textColor = null;
        }
    }

    public void setBackgroundVisible(final boolean selected) {
        backgroundVisible = selected;
        build();
    }

    public void setJSEnabled(boolean b) {
        CountDownLatch cdl = new CountDownLatch(1);
        Platform.runLater(() -> {
            try {
                view.getEngine().setJavaScriptEnabled(b);
            } finally {
                cdl.countDown();
            }
        });
        try {
            cdl.await();
        } catch (InterruptedException e) {
            OpenBST.LOG.error(e);
        }

        parent.getJSHint().setToolTipText(Lang.get("html.js" + (b ? "enabled" : "block")));
        parent.getJSHint().setIcon(new ImageIcon(b ? Icons.getImage("JSY", 16) : Icons.getImage("JSN", 16)));
        parent.getJSHint()
                .setDisabledIcon(new ImageIcon(b ? Icons.getImage("JSY", 16) : Icons.getImage("JSN", 16)));
        parent.getJSHint().setVisible(true);
    }

    public void setHrefEnabled(boolean b) {
        hrefEnabled = b;
        parent.getHrefHint().setToolTipText(Lang.get("html.href" + (b ? "enabled" : "block")));
        parent.getHrefHint().setIcon(new ImageIcon(b ? Icons.getImage("LinkY", 16) : Icons.getImage("LinkN", 16)));
        parent.getHrefHint()
                .setDisabledIcon(new ImageIcon(b ? Icons.getImage("LinkY", 16) : Icons.getImage("LinkN", 16)));
        parent.getHrefHint().setVisible(true);
    }

    public void dispose() {
        OpenBSTGUI.getInstance().removeDarkModeCallbback(callback);
    }

    @Experimental
    public void addCSSSheet(String sheet) {
        additionalCSS.add(sheet);
    }

    @Experimental
    public void removeCSSSheet(String sheet) {
        additionalCSS.remove(sheet);
    }

    @Experimental
    public void removeAllCSSSheets() {
        additionalCSS.clear();
    }

}