<commit_before>/**
 * 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;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FileDialog;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;

import org.apache.commons.lang.StringEscapeUtils;

import com.github.rjeschke.txtmark.Processor;
import com.google.gson.Gson;

import net.miginfocom.swing.MigLayout;
import utybo.branchingstorytree.api.BSTException;
import utybo.branchingstorytree.api.StoryUtils;
import utybo.branchingstorytree.api.script.ActionDescriptor;
import utybo.branchingstorytree.api.story.BranchingStory;
import utybo.branchingstorytree.api.story.LogicalNode;
import utybo.branchingstorytree.api.story.NodeOption;
import utybo.branchingstorytree.api.story.SaveState;
import utybo.branchingstorytree.api.story.StoryNode;
import utybo.branchingstorytree.api.story.TextNode;
import utybo.branchingstorytree.swing.JScrollablePanel.ScrollableSizeHint;

@SuppressWarnings("serial")
public class StoryPanel extends JPanel
{

    protected BranchingStory story;
    private StoryNode currentNode;
    private TabClient client;
    private SaveState latestSaveState;
    private File file;

    protected OpenBST parentWindow;
    private final JLabel textLabel;
    private final JLabel nodeIdLabel;
    private NodeOption[] options;
    private JButton[] optionsButton;
    private final JPanel panel = new JPanel();
    private Color normalButtonFg;

    private JButton restoreSaveStateButton, exportSaveStateButton;
    protected JToggleButton variableWatcherButton;
    protected VariableWatchDialog variableWatcher;

    public StoryPanel(BranchingStory story, OpenBST parentWindow, File f, TabClient client)
    {
        log("=> Initial setup");
        this.story = story;
        this.parentWindow = parentWindow;
        file = f;
        this.client = client;

        log("=> Creating visual elements");
        setLayout(new MigLayout("", "[grow]", "[][grow][]"));

        JToolBar toolBar = new JToolBar();
        toolBar.setFloatable(false);
        toolBar.add(new AbstractAction("Create Save State", new ImageIcon(OpenBST.saveAsImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                latestSaveState = new SaveState(currentNode.getId(), story.getRegistry());
                restoreSaveStateButton.setEnabled(true);
                exportSaveStateButton.setEnabled(true);
            }
        });
        restoreSaveStateButton = toolBar.add(new AbstractAction("Restore latest Save State", new ImageIcon(OpenBST.undoImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(JOptionPane.showConfirmDialog(parentWindow, "<html><body style='width: 300px'>You are about to go back to the latest save state. Are you sure you want to do this?", "Restore Save State confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, new ImageIcon(OpenBST.undoBigImage)) == JOptionPane.YES_OPTION)
                {
                    restoreSaveState(latestSaveState);
                }
            }
        });
        restoreSaveStateButton.setEnabled(false);
        exportSaveStateButton = toolBar.add(new AbstractAction("Export latest Save State", new ImageIcon(OpenBST.exportImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                final FileDialog jfc = new FileDialog(parentWindow, "Save State location", FileDialog.SAVE);
                jfc.setLocationRelativeTo(parentWindow);
                jfc.setIconImage(OpenBST.exportImage);
                jfc.setVisible(true);
                if(jfc.getFile() != null)
                {
                    File file = new File(jfc.getFile().endsWith(".bss") ? jfc.getDirectory() + jfc.getFile() : jfc.getDirectory() + jfc.getFile() + ".bss");
                    Gson gson = new Gson();
                    file.delete();
                    try
                    {
                        file.createNewFile();
                        FileWriter writer = new FileWriter(file);
                        gson.toJson(new SaveState(currentNode.getId(), story.getRegistry()), writer);
                        writer.flush();
                        writer.close();
                    }
                    catch(IOException e1)
                    {
                        e1.printStackTrace();
                        JOptionPane.showMessageDialog(parentWindow, "Could not save the file : " + e1.getMessage() + " (" + e1.getClass().getSimpleName() + ")");
                    }
                }
            }
        });
        exportSaveStateButton.setEnabled(false);
        toolBar.add(new AbstractAction("Import Save State", new ImageIcon(OpenBST.importImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                final FileDialog jfc = new FileDialog(parentWindow, "Save State location", FileDialog.LOAD);
                jfc.setLocationRelativeTo(parentWindow);
                jfc.setIconImage(OpenBST.importImage);
                jfc.setVisible(true);
                if(jfc.getFile() != null)
                {
                    File file = new File(jfc.getDirectory() + jfc.getFile());
                    Gson gson = new Gson();
                    try
                    {
                        FileReader reader = new FileReader(file);
                        latestSaveState = gson.fromJson(reader, SaveState.class);
                        reader.close();
                        restoreSaveState(latestSaveState);
                    }
                    catch(IOException e1)
                    {
                        e1.printStackTrace();
                        JOptionPane.showMessageDialog(parentWindow, "Could not save the file : " + e1.getMessage() + " (" + e1.getClass().getSimpleName() + ")");
                    }
                }
            }
        });
        toolBar.addSeparator();
        toolBar.add(new AbstractAction("Reset and restart from the beginning", new ImageIcon(OpenBST.returnImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(JOptionPane.showConfirmDialog(parentWindow, "<html><body style='width: 300px'>You are about to reset your progress and restart from the beginning. Are you sure you want to continue?", "Reset and Restart confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, new ImageIcon(OpenBST.returnBigImage)) == JOptionPane.YES_OPTION)
                {
                    reset();
                }
            }
        });
        toolBar.add(new AbstractAction("Soft Reload (reload the file and go back to where I left)", new ImageIcon(OpenBST.refreshImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(JOptionPane.showConfirmDialog(parentWindow, "<html><body style='width: 300px'>You are about to reload the BST file. We will try to restore where you were, but this is not a good idea if you heavily edited nodes and scripting. Are you sure you want to continue?", "Soft Reload confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, new ImageIcon(OpenBST.refreshBigImage)) == JOptionPane.YES_OPTION)
                {
                    SaveState ss = new SaveState(currentNode.getId(), story.getRegistry());
                    reload();
                    restoreSaveState(ss);
                }
            }
        });
        toolBar.add(new AbstractAction("Hard Reload (reload and reset)", new ImageIcon(OpenBST.synchronizeImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(JOptionPane.showConfirmDialog(parentWindow, "<html><body style='width: 300px'>You are about to reload the BST file. This will also reset all your progress. Are you sure you want to continue?", "Hard Reload confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, new ImageIcon(OpenBST.synchronizeBigImage)) == JOptionPane.YES_OPTION)
                {
                    reload();
                    reset();
                }
            }
        });
        toolBar.addSeparator();
        toolBar.add(new AbstractAction("Jump to Node...", new ImageIcon(OpenBST.jumpImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                SpinnerNumberModel model = new SpinnerNumberModel(1, 1, Integer.MAX_VALUE, 1);
                JSpinner spinner = new JSpinner(model);
                int i = JOptionPane.showOptionDialog(parentWindow, spinner, "Jump to Node number...", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, new ImageIcon(OpenBST.jumpBigImage), null, null);
                if(i == JOptionPane.OK_OPTION)
                {
                    showNode(story.getNode((Integer)spinner.getModel().getValue()));
                }
            }
        });
        variableWatcherButton = new JToggleButton("", new ImageIcon(OpenBST.addonSearchImage));
        variableWatcherButton.addItemListener(e ->
        {
            if(e.getStateChange() == ItemEvent.SELECTED)
            {
                variableWatcher = new VariableWatchDialog(StoryPanel.this);
                variableWatcher.setVisible(true);
            }
            else if(e.getStateChange() == ItemEvent.DESELECTED)
            {
                variableWatchClosing();
            }
        });
        variableWatcherButton.setToolTipText("Variable watcher...");
        toolBar.add(variableWatcherButton);

        toolBar.addSeparator();

        nodeIdLabel = new JLabel("Please wait...");
        nodeIdLabel.setVerticalAlignment(SwingConstants.CENTER);
        nodeIdLabel.setEnabled(false);
        toolBar.add(nodeIdLabel);

        toolBar.addSeparator();

        JLabel hintLabel = new JLabel("Hover on one of the icons for more details.");
        hintLabel.setEnabled(false);
        toolBar.add(hintLabel);

        toolBar.add(Box.createHorizontalGlue());

        toolBar.addSeparator();

        toolBar.add(new AbstractAction("Close tab", new ImageIcon(OpenBST.closeImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(JOptionPane.showConfirmDialog(parentWindow, "<html><body style='width: 300px'>You are about to close this BST file. All unsaved progress will be lost. Are you sure you want to close this tab?", "Tab close confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, new ImageIcon(OpenBST.closeBigImage)) == JOptionPane.YES_OPTION)
                {
                    parentWindow.removeStory(StoryPanel.this);
                }
            }
        });

        for(Component component : toolBar.getComponents())
        {
            if(component instanceof JButton)
            {
                ((JButton)component).setHideActionText(false);
                ((JButton)component).setToolTipText(((JButton)component).getText());
                ((JButton)component).setText("");
            }
        }
        add(toolBar, "cell 0 0,growx");

        final JScrollPane scrollPane = new JScrollPane();
        scrollPane.setBackground(Color.WHITE);
        add(scrollPane, "cell 0 1,grow");

        textLabel = new JLabel("<html>Please wait...");
        textLabel.setFont(new JTextArea().getFont());
        textLabel.setForeground(Color.BLACK);
        textLabel.setBorder(new EmptyBorder(10, 10, 10, 10));
        final JScrollablePanel jsp = new JScrollablePanel(new BorderLayout());
        jsp.add(textLabel, BorderLayout.CENTER);
        jsp.setScrollableWidth(ScrollableSizeHint.FIT);
        jsp.setScrollableHeight(ScrollableSizeHint.STRETCH);
        jsp.setBackground(Color.WHITE);
        scrollPane.setViewportView(jsp);

        add(panel, "cell 0 2,growx,aligny top");

        setupStory();
    }

    protected void restoreSaveState(SaveState ss)
    {
        ss.applySaveState(story);
        showNode(story.getNode(ss.getNodeId()));
    }

    private void setupStory()
    {<commit_msg>OpenBST : Fix vertical alignment<commit_after>/**
 * 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;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FileDialog;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;

import org.apache.commons.lang.StringEscapeUtils;

import com.github.rjeschke.txtmark.Processor;
import com.google.gson.Gson;

import net.miginfocom.swing.MigLayout;
import utybo.branchingstorytree.api.BSTException;
import utybo.branchingstorytree.api.StoryUtils;
import utybo.branchingstorytree.api.script.ActionDescriptor;
import utybo.branchingstorytree.api.story.BranchingStory;
import utybo.branchingstorytree.api.story.LogicalNode;
import utybo.branchingstorytree.api.story.NodeOption;
import utybo.branchingstorytree.api.story.SaveState;
import utybo.branchingstorytree.api.story.StoryNode;
import utybo.branchingstorytree.api.story.TextNode;
import utybo.branchingstorytree.swing.JScrollablePanel.ScrollableSizeHint;

@SuppressWarnings("serial")
public class StoryPanel extends JPanel
{

    protected BranchingStory story;
    private StoryNode currentNode;
    private TabClient client;
    private SaveState latestSaveState;
    private File file;

    protected OpenBST parentWindow;
    private final JLabel textLabel;
    private final JLabel nodeIdLabel;
    private NodeOption[] options;
    private JButton[] optionsButton;
    private final JPanel panel = new JPanel();
    private Color normalButtonFg;

    private JButton restoreSaveStateButton, exportSaveStateButton;
    protected JToggleButton variableWatcherButton;
    protected VariableWatchDialog variableWatcher;

    public StoryPanel(BranchingStory story, OpenBST parentWindow, File f, TabClient client)
    {
        log("=> Initial setup");
        this.story = story;
        this.parentWindow = parentWindow;
        file = f;
        this.client = client;

        log("=> Creating visual elements");
        setLayout(new MigLayout("", "[grow]", "[][grow][]"));

        JToolBar toolBar = new JToolBar();
        toolBar.setFloatable(false);
        toolBar.add(new AbstractAction("Create Save State", new ImageIcon(OpenBST.saveAsImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                latestSaveState = new SaveState(currentNode.getId(), story.getRegistry());
                restoreSaveStateButton.setEnabled(true);
                exportSaveStateButton.setEnabled(true);
            }
        });
        restoreSaveStateButton = toolBar.add(new AbstractAction("Restore latest Save State", new ImageIcon(OpenBST.undoImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(JOptionPane.showConfirmDialog(parentWindow, "<html><body style='width: 300px'>You are about to go back to the latest save state. Are you sure you want to do this?", "Restore Save State confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, new ImageIcon(OpenBST.undoBigImage)) == JOptionPane.YES_OPTION)
                {
                    restoreSaveState(latestSaveState);
                }
            }
        });
        restoreSaveStateButton.setEnabled(false);
        exportSaveStateButton = toolBar.add(new AbstractAction("Export latest Save State", new ImageIcon(OpenBST.exportImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                final FileDialog jfc = new FileDialog(parentWindow, "Save State location", FileDialog.SAVE);
                jfc.setLocationRelativeTo(parentWindow);
                jfc.setIconImage(OpenBST.exportImage);
                jfc.setVisible(true);
                if(jfc.getFile() != null)
                {
                    File file = new File(jfc.getFile().endsWith(".bss") ? jfc.getDirectory() + jfc.getFile() : jfc.getDirectory() + jfc.getFile() + ".bss");
                    Gson gson = new Gson();
                    file.delete();
                    try
                    {
                        file.createNewFile();
                        FileWriter writer = new FileWriter(file);
                        gson.toJson(new SaveState(currentNode.getId(), story.getRegistry()), writer);
                        writer.flush();
                        writer.close();
                    }
                    catch(IOException e1)
                    {
                        e1.printStackTrace();
                        JOptionPane.showMessageDialog(parentWindow, "Could not save the file : " + e1.getMessage() + " (" + e1.getClass().getSimpleName() + ")");
                    }
                }
            }
        });
        exportSaveStateButton.setEnabled(false);
        toolBar.add(new AbstractAction("Import Save State", new ImageIcon(OpenBST.importImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                final FileDialog jfc = new FileDialog(parentWindow, "Save State location", FileDialog.LOAD);
                jfc.setLocationRelativeTo(parentWindow);
                jfc.setIconImage(OpenBST.importImage);
                jfc.setVisible(true);
                if(jfc.getFile() != null)
                {
                    File file = new File(jfc.getDirectory() + jfc.getFile());
                    Gson gson = new Gson();
                    try
                    {
                        FileReader reader = new FileReader(file);
                        latestSaveState = gson.fromJson(reader, SaveState.class);
                        reader.close();
                        restoreSaveState(latestSaveState);
                    }
                    catch(IOException e1)
                    {
                        e1.printStackTrace();
                        JOptionPane.showMessageDialog(parentWindow, "Could not save the file : " + e1.getMessage() + " (" + e1.getClass().getSimpleName() + ")");
                    }
                }
            }
        });
        toolBar.addSeparator();
        toolBar.add(new AbstractAction("Reset and restart from the beginning", new ImageIcon(OpenBST.returnImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(JOptionPane.showConfirmDialog(parentWindow, "<html><body style='width: 300px'>You are about to reset your progress and restart from the beginning. Are you sure you want to continue?", "Reset and Restart confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, new ImageIcon(OpenBST.returnBigImage)) == JOptionPane.YES_OPTION)
                {
                    reset();
                }
            }
        });
        toolBar.add(new AbstractAction("Soft Reload (reload the file and go back to where I left)", new ImageIcon(OpenBST.refreshImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(JOptionPane.showConfirmDialog(parentWindow, "<html><body style='width: 300px'>You are about to reload the BST file. We will try to restore where you were, but this is not a good idea if you heavily edited nodes and scripting. Are you sure you want to continue?", "Soft Reload confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, new ImageIcon(OpenBST.refreshBigImage)) == JOptionPane.YES_OPTION)
                {
                    SaveState ss = new SaveState(currentNode.getId(), story.getRegistry());
                    reload();
                    restoreSaveState(ss);
                }
            }
        });
        toolBar.add(new AbstractAction("Hard Reload (reload and reset)", new ImageIcon(OpenBST.synchronizeImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(JOptionPane.showConfirmDialog(parentWindow, "<html><body style='width: 300px'>You are about to reload the BST file. This will also reset all your progress. Are you sure you want to continue?", "Hard Reload confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, new ImageIcon(OpenBST.synchronizeBigImage)) == JOptionPane.YES_OPTION)
                {
                    reload();
                    reset();
                }
            }
        });
        toolBar.addSeparator();
        toolBar.add(new AbstractAction("Jump to Node...", new ImageIcon(OpenBST.jumpImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                SpinnerNumberModel model = new SpinnerNumberModel(1, 1, Integer.MAX_VALUE, 1);
                JSpinner spinner = new JSpinner(model);
                int i = JOptionPane.showOptionDialog(parentWindow, spinner, "Jump to Node number...", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, new ImageIcon(OpenBST.jumpBigImage), null, null);
                if(i == JOptionPane.OK_OPTION)
                {
                    showNode(story.getNode((Integer)spinner.getModel().getValue()));
                }
            }
        });
        variableWatcherButton = new JToggleButton("", new ImageIcon(OpenBST.addonSearchImage));
        variableWatcherButton.addItemListener(e ->
        {
            if(e.getStateChange() == ItemEvent.SELECTED)
            {
                variableWatcher = new VariableWatchDialog(StoryPanel.this);
                variableWatcher.setVisible(true);
            }
            else if(e.getStateChange() == ItemEvent.DESELECTED)
            {
                variableWatchClosing();
            }
        });
        variableWatcherButton.setToolTipText("Variable watcher...");
        toolBar.add(variableWatcherButton);

        toolBar.addSeparator();

        nodeIdLabel = new JLabel("Please wait...");
        nodeIdLabel.setVerticalAlignment(SwingConstants.CENTER);
        nodeIdLabel.setEnabled(false);
        toolBar.add(nodeIdLabel);

        toolBar.addSeparator();

        JLabel hintLabel = new JLabel("Hover on one of the icons for more details.");
        hintLabel.setEnabled(false);
        toolBar.add(hintLabel);

        toolBar.add(Box.createHorizontalGlue());

        toolBar.addSeparator();

        toolBar.add(new AbstractAction("Close tab", new ImageIcon(OpenBST.closeImage))
        {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(JOptionPane.showConfirmDialog(parentWindow, "<html><body style='width: 300px'>You are about to close this BST file. All unsaved progress will be lost. Are you sure you want to close this tab?", "Tab close confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, new ImageIcon(OpenBST.closeBigImage)) == JOptionPane.YES_OPTION)
                {
                    parentWindow.removeStory(StoryPanel.this);
                }
            }
        });

        for(Component component : toolBar.getComponents())
        {
            if(component instanceof JButton)
            {
                ((JButton)component).setHideActionText(false);
                ((JButton)component).setToolTipText(((JButton)component).getText());
                ((JButton)component).setText("");
            }
        }
        add(toolBar, "cell 0 0,growx");

        final JScrollPane scrollPane = new JScrollPane();
        scrollPane.setBackground(Color.WHITE);
        add(scrollPane, "cell 0 1,grow");

        textLabel = new JLabel("<html>Please wait...");
        textLabel.setVerticalAlignment(SwingConstants.TOP);
        textLabel.setFont(new JTextArea().getFont());
        textLabel.setForeground(Color.BLACK);
        textLabel.setBorder(new EmptyBorder(10, 10, 10, 10));
        final JScrollablePanel jsp = new JScrollablePanel(new BorderLayout());
        jsp.add(textLabel, BorderLayout.CENTER);
        jsp.setScrollableWidth(ScrollableSizeHint.FIT);
        jsp.setScrollableHeight(ScrollableSizeHint.STRETCH);
        jsp.setBackground(Color.WHITE);
        scrollPane.setViewportView(jsp);

        add(panel, "cell 0 2,growx,aligny top");

        setupStory();
    }

    protected void restoreSaveState(SaveState ss)
    {
        ss.applySaveState(story);
        showNode(story.getNode(ss.getNodeId()));
    }

    private void setupStory()
    {
{'fraction_non_alphanumeric': 0.09399834585925419, 'fraction_numerical': 0.003955553957351936, 'mean_word_length': 2.2740758182246292, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7590627', 'n_tokens_mistral': 7520, 'n_tokens_neox': 7191, 'n_words': 1662}