// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.resource.dlg;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Stack;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.JViewport;
import javax.swing.ProgressMonitor;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.infinity.NearInfinity;
import org.infinity.datatype.Flag;
import org.infinity.datatype.ResourceRef;
import org.infinity.datatype.SectionCount;
import org.infinity.gui.BrowserMenuBar;
import org.infinity.gui.LinkButton;
import org.infinity.gui.ScriptTextArea;
import org.infinity.gui.ViewFrame;
import org.infinity.gui.ViewerUtil;
import org.infinity.gui.WindowBlocker;
import org.infinity.icon.Icons;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.StructEntry;
import org.infinity.util.StringResource;
/** Show dialog content as tree structure. */
final class TreeViewer extends JPanel implements ActionListener, TreeSelectionListener,
TableModelListener, PropertyChangeListener
{
private final JPopupMenu pmTree = new JPopupMenu();
private final JMenuItem miExpandAll = new JMenuItem("Expand all nodes");
private final JMenuItem miExpand = new JMenuItem("Expand selected node");
private final JMenuItem miCollapseAll = new JMenuItem("Collapse all nodes");
private final JMenuItem miCollapse = new JMenuItem("Collapse selected nodes");
private final JMenuItem miEditEntry = new JMenuItem("Edit selected entry");
// caches ViewFrame instances used to display external dialog entries
private final HashMap<String, ViewFrame> mapViewer = new HashMap<String, ViewFrame>();
private final DlgResource dlg;
private final JTree dlgTree;
private final ItemInfo dlgInfo;
private DlgTreeModel dlgModel;
private JScrollPane spInfo, spTree;
private TreeWorker worker;
private WindowBlocker blocker;
TreeViewer(DlgResource dlg)
{
super(new BorderLayout());
this.dlg = dlg;
this.dlg.addTableModelListener(this);
dlgModel = null;
dlgTree = new JTree((TreeModel)null);
dlgTree.addTreeSelectionListener(this);
dlgInfo = new ItemInfo();
initControls();
}
//--------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == miEditEntry) {
TreePath path = dlgTree.getSelectionPath();
if (path != null && path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
if (node.getUserObject() instanceof ItemBase) {
ItemBase item = (ItemBase)node.getUserObject();
boolean isExtern = (!item.getDialogName().equals(dlg.getResourceEntry().getResourceName()));
if (isExtern) {
ViewFrame vf = mapViewer.get(item.getDialogName().toUpperCase(Locale.ENGLISH));
// reuseing external dialog window if possible
if (vf != null && vf.isVisible()) {
vf.toFront();
} else {
vf = new ViewFrame(this, item.getDialog());
mapViewer.put(item.getDialogName().toUpperCase(Locale.ENGLISH), vf);
}
}
if (item.getDialog().getViewer() != null) {
// selecting table entry
Viewer viewer = (Viewer)item.getDialog().getViewerTab(0);
if (node.getUserObject() instanceof RootItem) {
item.getDialog().getViewer().selectEntry(0);
} else if (node.getUserObject() instanceof StateItem) {
int stateIdx = ((StateItem)item).getState().getNumber();
item.getDialog().getViewer().selectEntry(State.DLG_STATE + " " + stateIdx);
viewer.showStateWithStructEntry(((StateItem)item).getState());
} else if (node.getUserObject() instanceof TransitionItem) {
int transIdx = ((TransitionItem)item).getTransition().getNumber();
item.getDialog().getViewer().selectEntry(Transition.DLG_TRANS + " " + transIdx);
viewer.showStateWithStructEntry(((TransitionItem)item).getTransition());
}
item.getDialog().selectEditTab();
}
}
}
} else if (e.getSource() == miExpandAll) {
if (worker == null) {
worker = new TreeWorker(this, TreeWorker.Type.EXPAND, new TreePath(dlgModel.getRoot()));
worker.addPropertyChangeListener(this);
blocker = new WindowBlocker(NearInfinity.getInstance());
blocker.setBlocked(true);
worker.execute();
}
} else if (e.getSource() == miCollapseAll) {
if (worker == null) {
worker = new TreeWorker(this, TreeWorker.Type.COLLAPSE, new TreePath(dlgModel.getRoot()));
worker.addPropertyChangeListener(this);
blocker = new WindowBlocker(NearInfinity.getInstance());
blocker.setBlocked(true);
worker.execute();
}
} else if (e.getSource() == miExpand) {
if (worker == null) {
worker = new TreeWorker(this, TreeWorker.Type.EXPAND, dlgTree.getSelectionPath());
worker.addPropertyChangeListener(this);
blocker = new WindowBlocker(NearInfinity.getInstance());
blocker.setBlocked(true);
worker.execute();
}
} else if (e.getSource() == miCollapse) {
if (worker == null) {
worker = new TreeWorker(this, TreeWorker.Type.COLLAPSE, dlgTree.getSelectionPath());
worker.addPropertyChangeListener(this);
blocker = new WindowBlocker(NearInfinity.getInstance());
blocker.setBlocked(true);
worker.execute();
}
}
}
//--------------------- End Interface ActionListener ---------------------
//--------------------- Begin Interface TreeSelectionListener ---------------------
@Override
public void valueChanged(TreeSelectionEvent e)
{
if (e.getSource() == dlgTree) {
Object node = dlgTree.getLastSelectedPathComponent();
if (node instanceof DefaultMutableTreeNode) {
Object data = ((DefaultMutableTreeNode)node).getUserObject();
if (data instanceof StateItem) {
// dialog state found
updateStateInfo((StateItem)data);
} else if (data instanceof TransitionItem) {
// dialog response found
updateTransitionInfo((TransitionItem)data);
} else {
// no valid type found
dlgInfo.showPanel(ItemInfo.CARD_EMPTY);
}
} else {
// no node selected
dlgInfo.showPanel(ItemInfo.CARD_EMPTY);
}
}
}
//--------------------- End Interface TreeSelectionListener ---------------------
//--------------------- Begin Interface TableModelListener ---------------------
@Override
public void tableChanged(TableModelEvent e)
{
// Insertion or removal of nodes not yet supported
if (e.getType() == TableModelEvent.UPDATE) {
if (e.getSource() instanceof State) {
State state = (State)e.getSource();
dlgModel.updateState(state);
} else if (e.getSource() instanceof Transition) {
Transition trans = (Transition)e.getSource();
dlgModel.updateTransition(trans);
} else if (e.getSource() instanceof DlgResource) {
dlgModel.updateRoot();
}
}
}
//--------------------- End Interface TableModelListener ---------------------
//--------------------- Begin Interface PropertyChangeListener ---------------------
@Override
public void propertyChange(PropertyChangeEvent event)
{
if (event.getSource() == worker) {
if ("state".equals(event.getPropertyName()) &&
TreeWorker.StateValue.DONE == event.getNewValue()) {
if (blocker != null) {
blocker.setBlocked(false);
blocker = null;
}
worker = null;
}
}
}
//--------------------- End Interface PropertyChangeListener ---------------------
/** Initializes the actual dialog content. */
public void init()
{
if (dlgModel == null) {
dlgModel = new DlgTreeModel(this.dlg);
dlgTree.setModel(dlgModel);
}
}
private void updateStateInfo(StateItem si)
{
if (si != null && si.getDialog() != null && si.getState() != null) {
DlgResource curDlg = si.getDialog();
State state = si.getState();
boolean showStrrefs = BrowserMenuBar.getInstance().showStrrefs();
// updating info box title
StringBuilder sb = new StringBuilder(state.getName() + ", ");
if (curDlg != dlg) {
sb.append(String.format("Dialog: %1$s, ", curDlg.getResourceEntry().getResourceName()));
}
sb.append(String.format("Responses: %1$d", state.getTransCount()));
if (state.getTriggerIndex() >= 0) {
sb.append(String.format(", Weight: %1$d", state.getTriggerIndex()));
}
dlgInfo.updateControlBorder(ItemInfo.Type.STATE, sb.toString());
// updating state text
dlgInfo.showControl(ItemInfo.Type.STATE_TEXT, true);
dlgInfo.updateControlText(ItemInfo.Type.STATE_TEXT,
StringResource.getStringRef(state.getResponse().getValue(),
showStrrefs));
// updating state WAV Res
String responseText = StringResource.getWavResource(state.getResponse().getValue());
if (responseText != null) {
dlgInfo.showControl(ItemInfo.Type.STATE_WAV, true);
dlgInfo.updateControlText(ItemInfo.Type.STATE_WAV, responseText + ".WAV");
} else {
dlgInfo.showControl(ItemInfo.Type.STATE_WAV, false);
}
// updating state triggers
if (state.getTriggerIndex() >= 0) {
dlgInfo.showControl(ItemInfo.Type.STATE_TRIGGER, true);
StructEntry entry = curDlg.getAttribute(StateTrigger.DLG_STATETRIGGER + " " + state.getTriggerIndex());
if (entry instanceof StateTrigger) {
dlgInfo.updateControlText(ItemInfo.Type.STATE_TRIGGER, ((StateTrigger)entry).toString());
} else {
dlgInfo.updateControlText(ItemInfo.Type.STATE_TRIGGER, "");
}
} else {
dlgInfo.showControl(ItemInfo.Type.STATE_TRIGGER, false);
}
dlgInfo.showPanel(ItemInfo.CARD_STATE);
// jumping to top of scroll area
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() { spInfo.getVerticalScrollBar().setValue(0); }
});
} else {
dlgInfo.showPanel(ItemInfo.CARD_EMPTY);
}
}
private void updateTransitionInfo(TransitionItem ti)
{
if (ti != null && ti.getDialog() != null && ti.getTransition() != null) {
DlgResource curDlg = ti.getDialog();
Transition trans = ti.getTransition();
boolean showStrrefs = BrowserMenuBar.getInstance().showStrrefs();
StructEntry entry;
// updating info box title
StringBuilder sb = new StringBuilder(trans.getName());
if (curDlg != dlg) {
sb.append(String.format(", Dialog: %1$s", curDlg.getResourceEntry().getResourceName()));
}
dlgInfo.updateControlBorder(ItemInfo.Type.RESPONSE, sb.toString());
// updating flags
dlgInfo.showControl(ItemInfo.Type.RESPONSE_FLAGS, true);
dlgInfo.updateControlText(ItemInfo.Type.RESPONSE_FLAGS, trans.getFlag().toString());
// updating response text
if (trans.getFlag().isFlagSet(0)) {
dlgInfo.showControl(ItemInfo.Type.RESPONSE_TEXT, true);
dlgInfo.updateControlText(ItemInfo.Type.RESPONSE_TEXT,
StringResource.getStringRef(trans.getAssociatedText().getValue(),
showStrrefs));
} else {
dlgInfo.showControl(ItemInfo.Type.RESPONSE_TEXT, false);
}
// updating journal entry
if (trans.getFlag().isFlagSet(4)) {
dlgInfo.showControl(ItemInfo.Type.RESPONSE_JOURNAL, true);
dlgInfo.updateControlText(ItemInfo.Type.RESPONSE_JOURNAL,
StringResource.getStringRef(trans.getJournalEntry().getValue(),
showStrrefs));
} else {
dlgInfo.showControl(ItemInfo.Type.RESPONSE_JOURNAL, false);
}
// updating response trigger
if (trans.getFlag().isFlagSet(1)) {
dlgInfo.showControl(ItemInfo.Type.RESPONSE_TRIGGER, true);
entry = curDlg.getAttribute(ResponseTrigger.DLG_RESPONSETRIGGER + " " + trans.getTriggerIndex());
if (entry instanceof ResponseTrigger) {
dlgInfo.updateControlText(ItemInfo.Type.RESPONSE_TRIGGER, ((ResponseTrigger)entry).toString());
} else {
dlgInfo.updateControlText(ItemInfo.Type.RESPONSE_TRIGGER, "");
}
} else {
dlgInfo.showControl(ItemInfo.Type.RESPONSE_TRIGGER, false);
}
// updating action
if (trans.getFlag().isFlagSet(2)) {
dlgInfo.showControl(ItemInfo.Type.RESPONSE_ACTION, true);
entry = curDlg.getAttribute(Action.DLG_ACTION + " " + trans.getActionIndex());
if (entry instanceof Action) {
dlgInfo.updateControlText(ItemInfo.Type.RESPONSE_ACTION, ((Action)entry).toString());
} else {
dlgInfo.updateControlText(ItemInfo.Type.RESPONSE_ACTION, "");
}
} else {
dlgInfo.showControl(ItemInfo.Type.RESPONSE_ACTION, false);
}
dlgInfo.showPanel(ItemInfo.CARD_RESPONSE);
// jumping to top of scroll area
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() { spInfo.getVerticalScrollBar().setValue(0); }
});
} else {
dlgInfo.showPanel(ItemInfo.CARD_EMPTY);
}
}
private void initControls()
{
// initializing info component
spInfo = new JScrollPane(dlgInfo, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
spInfo.getViewport().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e)
{
// never scroll horizontally
JViewport vp = (JViewport)e.getSource();
if (vp != null) {
Dimension d = vp.getExtentSize();
if (d.width != vp.getView().getWidth()) {
d.height = vp.getView().getHeight();
vp.getView().setSize(d);
}
}
}
});
spInfo.getVerticalScrollBar().setUnitIncrement(16);
// initializing tree component
JPanel pTree = new JPanel(new GridBagLayout());
pTree.setBackground(dlgTree.getBackground());
GridBagConstraints gbc = new GridBagConstraints();
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.BOTH, new Insets(4, 4, 4, 4), 0, 0);
pTree.add(dlgTree, gbc);
spTree = new JScrollPane(pTree);
spTree.setBorder(BorderFactory.createEmptyBorder());
spTree.getHorizontalScrollBar().setUnitIncrement(16);
spTree.getVerticalScrollBar().setUnitIncrement(16);
dlgTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
dlgTree.setRootVisible(true);
dlgTree.setEditable(false);
DefaultTreeCellRenderer tcr = (DefaultTreeCellRenderer)dlgTree.getCellRenderer();
tcr.setLeafIcon(null);
tcr.setOpenIcon(null);
tcr.setClosedIcon(null);
// drawing custom icons for each node type
dlgTree.setCellRenderer(new DefaultTreeCellRenderer() {
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,
boolean expanded, boolean leaf, int row,
boolean focused)
{
Component c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, focused);
if (value instanceof DefaultMutableTreeNode) {
Object data = ((DefaultMutableTreeNode)value).getUserObject();
if (data instanceof ItemBase) {
setIcon(((ItemBase)data).getIcon());
} else {
setIcon(null);
}
}
return c;
}
});
// preventing root node from collapsing
dlgTree.addTreeWillExpandListener(new TreeWillExpandListener() {
@Override
public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException
{
}
@Override
public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException
{
if (event.getPath().getLastPathComponent() == dlgModel.getRoot()) {
throw new ExpandVetoException(event);
}
}
});
// setting model AFTER customizing visual appearance of the tree control
// dlgTree.setModel(dlgModel);
// initializing popup menu
miEditEntry.addActionListener(this);
miEditEntry.setEnabled(!dlgTree.isSelectionEmpty());
miExpand.addActionListener(this);
miExpand.setEnabled(!dlgTree.isSelectionEmpty());
miCollapse.addActionListener(this);
miCollapse.setEnabled(!dlgTree.isSelectionEmpty());
miExpandAll.addActionListener(this);
miCollapseAll.addActionListener(this);
pmTree.add(miEditEntry);
pmTree.addSeparator();
pmTree.add(miExpand);
pmTree.add(miCollapse);
pmTree.add(miExpandAll);
pmTree.add(miCollapseAll);
dlgTree.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e) { maybeShowPopup(e); }
@Override
public void mouseReleased(MouseEvent e) { maybeShowPopup(e); }
private void maybeShowPopup(MouseEvent e)
{
if (e.getSource() == dlgTree && e.isPopupTrigger()) {
miEditEntry.setEnabled(!dlgTree.isSelectionEmpty());
miExpand.setEnabled(!dlgTree.isSelectionEmpty() &&
!isNodeExpanded(dlgTree.getSelectionPath()) &&
dlgTree.getSelectionPath().getPathCount() > 1);
miCollapse.setEnabled(!dlgTree.isSelectionEmpty() &&
!isNodeCollapsed(dlgTree.getSelectionPath()) &&
dlgTree.getSelectionPath().getPathCount() > 1);
pmTree.show(dlgTree, e.getX(), e.getY());
}
}
});
// putting components together
JSplitPane splitv = new JSplitPane(JSplitPane.VERTICAL_SPLIT, spTree, spInfo);
splitv.setDividerLocation(2 * NearInfinity.getInstance().getContentPane().getHeight() / 5);
add(splitv, BorderLayout.CENTER);
}
// Checks whether given path contains a node with the specified item object
private boolean nodeExists(TreePath path, Object item)
{
if (path != null && item != null) {
final Object[] nodes = path.getPath();
if (nodes != null) {
for (int i = 1; i < nodes.length; i++) {
if (nodes[i] instanceof DefaultMutableTreeNode) {
final Object curItem = ((DefaultMutableTreeNode)nodes[i]).getUserObject();
if (item.equals(curItem)) {
return true;
}
}
}
}
}
return false;
}
// Expands all children and their children of the given path
private void expandNode(TreePath path)
{
final TreePath curPath = path;
if (worker != null && worker.userCancelled()) return;
if (path != null) {
TreeNode node = (TreeNode)path.getLastPathComponent();
final Object item = ((DefaultMutableTreeNode)node).getUserObject();
if (item instanceof StateItem) {
if (nodeExists(path.getParentPath(), item)) {
return;
}
}
if (worker != null) { worker.advanceProgress(); }
if (!dlgTree.isExpanded(path)) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() { dlgTree.expandPath(curPath); }
});
} catch (InterruptedException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
for (int i = 0, count = node.getChildCount(); i < count; i++) {
expandNode(curPath.pathByAddingChild(node.getChildAt(i)));
if (worker != null && worker.userCancelled()) return;
}
}
}
// Collapses all children and their children of the given path
private void collapseNode(TreePath path)
{
final TreePath curPath = path;
if (worker != null && worker.userCancelled()) return;
if (path != null) {
TreeNode node = (TreeNode)path.getLastPathComponent();
for (int i = 0; i < node.getChildCount(); i++) {
collapseNode(curPath.pathByAddingChild(node.getChildAt(i)));
if (worker != null && worker.userCancelled()) return;
}
if (worker != null) { worker.advanceProgress(); }
if (!dlgTree.isCollapsed(path)) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() { dlgTree.collapsePath(curPath); }
});
} catch (InterruptedException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
// Returns true if the given path contains expanded nodes
private boolean isNodeExpanded(TreePath path)
{
boolean retVal = true;
if (path != null) {
// evaluating current node
TreeNode node = (TreeNode)path.getLastPathComponent();
if (!node.isLeaf()) {
retVal = dlgTree.isExpanded(path);
// traversing child nodes
if (retVal) {
for (int i = 0; i < node.getChildCount(); i++) {
retVal = isNodeExpanded(path.pathByAddingChild(node.getChildAt(i)));
if (!retVal) break;
}
}
}
}
return retVal;
}
// Returns true if the given path contains collapsed nodes
private boolean isNodeCollapsed(TreePath path)
{
boolean retVal = true;
if (path != null) {
// evaluating current node
TreeNode node = (TreeNode)path.getLastPathComponent();
if (!node.isLeaf()) {
retVal = dlgTree.isCollapsed(path);
// traversing child nodes
if (retVal) {
for (int i = 0; i < node.getChildCount(); i++) {
retVal = isNodeCollapsed(path.pathByAddingChild(node.getChildAt(i)));
if (retVal) break;
}
}
}
}
return retVal;
}
//-------------------------- INNER CLASSES --------------------------
// Applies expand or collapse operations on a set of dialog tree nodes in a background task
private static class TreeWorker extends SwingWorker<Void, Void>
{
// Supported operations
public enum Type { EXPAND, COLLAPSE }
private final TreeViewer instance;
private final Type type;
private final TreePath path;
private ProgressMonitor progress;
public TreeWorker(TreeViewer instance, Type type, TreePath path)
{
this.instance = instance;
this.type = type;
this.path = path;
String msg;
switch (getType()) {
case EXPAND:
msg = "Expanding nodes";
break;
case COLLAPSE:
msg = "Collapsing nodes";
break;
default:
msg = "";
}
progress = new ProgressMonitor(this.instance, msg, "This may take a while...", 0, 1);
progress.setMillisToDecideToPopup(250);
progress.setMillisToPopup(1000);
progress.setProgress(0);
}
@Override
protected Void doInBackground() throws Exception
{
try {
switch (getType()) {
case EXPAND:
instance.expandNode(path);
break;
case COLLAPSE:
instance.collapseNode(path);
break;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected void done()
{
if (progress != null) {
progress.close();
progress = null;
}
}
/** Current operation type. */
public Type getType()
{
return type;
}
/** Advances the progress bar by one unit. May display a short notice after a while. */
public void advanceProgress()
{
if (progress != null) {
progress.setMaximum(progress.getMaximum() + 1);
if ((progress.getMaximum() - 1) % 100 == 0) {
progress.setNote(String.format("Processing node %1$d", progress.getMaximum() - 1));
}
progress.setProgress(progress.getMaximum() - 1);
}
}
/** Returns true if the user cancelled the operation. */
public boolean userCancelled()
{
if (progress != null) {
return progress.isCanceled();
}
return false;
}
}
// Common base class for node type specific classes
private static abstract class ItemBase
{
private final DlgResource dlg;
private final boolean showStrrefs;
private final boolean showIcons;
public ItemBase(DlgResource dlg)
{
this.dlg = dlg;
this.showStrrefs = BrowserMenuBar.getInstance().showStrrefs();
this.showIcons = BrowserMenuBar.getInstance().showDlgTreeIcons();
}
/** Returns the dialog resource object. */
public DlgResource getDialog()
{
return dlg;
}
/** Returns the dialog resource name. */
public String getDialogName()
{
if (dlg != null) {
return dlg.getResourceEntry().getResourceName();
} else {
return "";
}
}
/** Returns the icon associated with the item type. */
public abstract Icon getIcon();
/** Returns whether to show the Strref value next to the string. */
protected boolean showStrrefs() { return showStrrefs; }
/** Returns whether to display icons in front of the nodes. */
protected boolean showIcons() { return showIcons; }
}
// Meta class for identifying root node
private static final class RootItem extends ItemBase
{
private static final ImageIcon ICON = Icons.getIcon(Icons.ICON_ROW_INSERT_AFTER_16);
private final ArrayList<StateItem> states = new ArrayList<StateItem>();
private final ImageIcon icon;
private int numStates, numTransitions, numStateTriggers, numResponseTriggers, numActions;
private String flags;
public RootItem(DlgResource dlg)
{
super(dlg);
this.icon = showIcons() ? ICON : null;
if (getDialog() != null) {
StructEntry entry = getDialog().getAttribute(DlgResource.DLG_NUM_STATES);
if (entry instanceof SectionCount) {
numStates = ((SectionCount)entry).getValue();
}
entry = getDialog().getAttribute(DlgResource.DLG_NUM_RESPONSES);
if (entry instanceof SectionCount) {
numTransitions = ((SectionCount)entry).getValue();
}
entry = getDialog().getAttribute(DlgResource.DLG_NUM_STATE_TRIGGERS);
if (entry instanceof SectionCount) {
numStateTriggers = ((SectionCount)entry).getValue();
}
entry = getDialog().getAttribute(DlgResource.DLG_NUM_RESPONSE_TRIGGERS);
if (entry instanceof SectionCount) {
numResponseTriggers = ((SectionCount)entry).getValue();
}
entry = getDialog().getAttribute(DlgResource.DLG_NUM_ACTIONS);
if (entry instanceof SectionCount) {
numActions = ((SectionCount)entry).getValue();
}
entry = getDialog().getAttribute(DlgResource.DLG_THREAT_RESPONSE);
if (entry instanceof Flag) {
flags = ((Flag)entry).toString();
}
// finding and storing initial states (sorted by trigger index in ascending order)
for (int i = 0; i < numStates; i++) {
entry = getDialog().getAttribute(State.DLG_STATE + " " + i);
if (entry instanceof State) {
int triggerIndex = ((State)entry).getTriggerIndex();
if (triggerIndex >= 0) {
int j = 0;
for (; j < states.size(); j++) {
if (states.get(j).getState().getTriggerIndex() > triggerIndex) {
break;
}
}
states.add(j, new StateItem(getDialog(), (State)entry));
}
}
}
}
}
/** Returns number of available initial states. */
public int getInitialStateCount()
{
return states.size();
}
/** Returns the StateItem at the given index or null on error. */
public StateItem getInitialState(int index)
{
if (index >= 0 && index < states.size()) {
return states.get(index);
}
return null;
}
@Override
public Icon getIcon()
{
return icon;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if (!getDialogName().isEmpty()) {
sb.append(getDialogName());
} else {
sb.append("(Invalid DLG resource)");
}
sb.append(" (states: ").append(Integer.toString(numStates));
sb.append(", responses: ").append(Integer.toString(numTransitions));
sb.append(", state triggers: ").append(Integer.toString(numStateTriggers));
sb.append(", response triggers: ").append(Integer.toString(numResponseTriggers));
sb.append(", actions: ").append(Integer.toString(numActions));
if (flags != null) {
sb.append(", flags: ").append(flags);
}
sb.append(")");
return sb.toString();
}
}
// Encapsulates a dialog state entry
private static final class StateItem extends ItemBase
{
private static final ImageIcon ICON = Icons.getIcon(Icons.ICON_STOP_16);
private static final int MAX_LENGTH = 100; // max. string length to display
private final ImageIcon icon;
private State state;
public StateItem(DlgResource dlg, State state)
{
super(dlg);
this.icon = showIcons() ? ICON : null;
this.state = state;
}
public State getState()
{
return state;
}
public void setState(State state)
{
if (state != null) {
this.state = state;
}
}
@Override
public Icon getIcon()
{
return icon;
}
@Override
public String toString()
{
if (state != null) {
String text = StringResource.getStringRef(state.getResponse().getValue(), showStrrefs(), true);
if (text.length() > MAX_LENGTH) {
text = text.substring(0, MAX_LENGTH) + "...";
}
return String.format("%1$s: %2$s", state.getName(), text);
} else {
return "(Invalid state)";
}
}
}
// Encapsulates a dialog transition entry
private static final class TransitionItem extends ItemBase
{
private static final ImageIcon ICON = Icons.getIcon(Icons.ICON_PLAY_16);
private static final int MAX_LENGTH = 100; // max. string length to display
private final ImageIcon icon;
private Transition trans;
public TransitionItem(DlgResource dlg, Transition trans)
{
super(dlg);
this.icon = showIcons() ? ICON : null;
this.trans = trans;
}
public Transition getTransition()
{
return trans;
}
public void setTransition(Transition trans)
{
if (trans != null) {
this.trans = trans;
}
}
@Override
public Icon getIcon()
{
return icon;
}
@Override
public String toString()
{
if (trans != null) {
if (trans.getFlag().isFlagSet(0)) {
// Transition contains text
String text = StringResource.getStringRef(trans.getAssociatedText().getValue(), showStrrefs(), true);
if (text.length() > MAX_LENGTH) {
text = text.substring(0, MAX_LENGTH) + "...";
}
String dlg = getDialog().getResourceEntry().getResourceName();
if (trans.getNextDialog().isEmpty() ||
trans.getNextDialog().getResourceName().equalsIgnoreCase(dlg)) {
return String.format("%1$s: %2$s", trans.getName(), text);
} else {
return String.format("%1$s: %2$s [%3$s]",
trans.getName(), text, trans.getNextDialog().getResourceName());
}
} else {
// Transition contains no text
String dlg = getDialog().getResourceEntry().getResourceName();
if (trans.getNextDialog().isEmpty() ||
trans.getNextDialog().getResourceName().equalsIgnoreCase(dlg)) {
return String.format("%1$s: (No text)", trans.getName());
} else {
return String.format("%1$s: (No text) [%2$s]",
trans.getName(), trans.getNextDialog().getResourceName());
}
}
} else {
return "(Invalid response)";
}
}
}
// Creates and manages the dialog tree structure
private static final class DlgTreeModel implements TreeModel
{
private final ArrayList<TreeModelListener> listeners = new ArrayList<TreeModelListener>();
// maps dialog resources to tables of state index/item pairs
private final HashMap<String, HashMap<Integer, StateItem>> mapState = new HashMap<String, HashMap<Integer, StateItem>>();
// maps dialog resources to tables of transition index/item pairs
private final HashMap<String, HashMap<Integer, TransitionItem>> mapTransition = new HashMap<String, HashMap<Integer, TransitionItem>>();
private RootItem root;
private DlgResource dlg;
private DefaultMutableTreeNode nodeRoot;
public DlgTreeModel(DlgResource dlg)
{
reset(dlg);
}
//--------------------- Begin Interface TreeModel ---------------------
@Override
public Object getRoot()
{
return updateNodeChildren(nodeRoot);
}
@Override
public Object getChild(Object parent, int index)
{
DefaultMutableTreeNode node = null;
if (parent instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode nodeParent = (DefaultMutableTreeNode)parent;
nodeParent = updateNodeChildren(nodeParent);
if (index >= 0 && index < nodeParent.getChildCount()) {
node = (DefaultMutableTreeNode)nodeParent.getChildAt(index);
}
}
return updateNodeChildren(node);
}
@Override
public int getChildCount(Object parent)
{
if (parent instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode nodeParent = (DefaultMutableTreeNode)parent;
nodeParent = updateNodeChildren(nodeParent);
return nodeParent.getChildCount();
}
return 0;
}
@Override
public boolean isLeaf(Object node)
{
if (node instanceof DefaultMutableTreeNode) {
return ((DefaultMutableTreeNode)node).isLeaf();
}
return false;
}
@Override
public void valueForPathChanged(TreePath path, Object newValue)
{
// immutable
}
@Override
public int getIndexOfChild(Object parent, Object child)
{
if (parent instanceof DefaultMutableTreeNode && child instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode nodeParent = (DefaultMutableTreeNode)parent;
for (int i = 0; i < nodeParent.getChildCount(); i++) {
TreeNode nodeChild = nodeParent.getChildAt(i);
if (nodeChild == child) {
return i;
}
}
}
return -1;
}
@Override
public void addTreeModelListener(TreeModelListener l)
{
if (l != null && !listeners.contains(l)) {
listeners.add(l);
}
}
@Override
public void removeTreeModelListener(TreeModelListener l)
{
if (l != null) {
int idx = listeners.indexOf(l);
if (idx >= 0) {
listeners.remove(idx);
}
}
}
//--------------------- End Interface TreeModel ---------------------
public void nodeChanged(TreeNode node)
{
if (node != null) {
if (node.getParent() == null) {
fireTreeNodesChanged(this, null, null, null);
} else {
fireTreeNodesChanged(this, createNodePath(node.getParent()),
new int[]{getChildNodeIndex(node)}, new Object[]{node});
}
}
}
public void nodeStructureChanged(TreeNode node)
{
if (node.getParent() == null) {
fireTreeStructureChanged(this, null, null, null);
} else {
fireTreeStructureChanged(this, createNodePath(node.getParent()),
new int[getChildNodeIndex(node)], new Object[]{node});
}
}
/** Removes any old content and re-initializes the model with the data from the given dialog resource. */
public void reset(DlgResource dlg)
{
// clearing maps
Iterator<String> iter = mapState.keySet().iterator();
while (iter.hasNext()) {
HashMap<Integer, StateItem> map = mapState.get(iter.next());
if (map != null) {
map.clear();
}
}
mapState.clear();
iter = mapTransition.keySet().iterator();
while (iter.hasNext()) {
HashMap<Integer, TransitionItem> map = mapTransition.get(iter.next());
if (map != null) {
map.clear();
}
}
mapTransition.clear();
root = null;
nodeRoot = null;
this.dlg = dlg;
root = new RootItem(dlg);
for (int i = 0; i < root.getInitialStateCount(); i++) {
initState(root.getInitialState(i));
}
nodeRoot = new DefaultMutableTreeNode(root, true);
// notifying listeners
nodeStructureChanged((DefaultMutableTreeNode)getRoot());
}
public void updateState(State state)
{
if (state != null) {
int stateIdx = state.getNumber();
HashMap<Integer, StateItem> map = getStateTable(dlg.getResourceEntry().getResourceName());
if (map != null) {
Iterator<Integer> iter = map.keySet().iterator();
while (iter.hasNext()) {
StateItem item = map.get(iter.next());
if (item != null && item.getState().getNumber() == stateIdx) {
item.setState(state);
triggerNodeChanged((DefaultMutableTreeNode)getRoot(), item);
break;
}
}
}
}
}
public void updateTransition(Transition trans)
{
if (trans != null) {
int transIdx = trans.getNumber();
HashMap<Integer, TransitionItem> map = getTransitionTable(dlg.getResourceEntry().getResourceName());
if (map != null) {
Iterator<Integer> iter = map.keySet().iterator();
while (iter.hasNext()) {
TransitionItem item = map.get(iter.next());
if (item != null && item.getTransition().getNumber() == transIdx) {
item.setTransition(trans);
triggerNodeChanged((DefaultMutableTreeNode)getRoot(), item);
break;
}
}
}
}
}
public void updateRoot()
{
root = new RootItem(dlg);
nodeRoot.setUserObject(root);
nodeChanged(nodeRoot);
}
// Recursively parses the tree and triggers a nodeChanged event for each node containing data.
private void triggerNodeChanged(DefaultMutableTreeNode node, Object data)
{
if (node != null && data != null) {
if (node.getUserObject() == data) {
nodeChanged(node);
}
for (int i = 0; i < node.getChildCount(); i++) {
triggerNodeChanged((DefaultMutableTreeNode)node.getChildAt(i), data);
}
}
}
// Generates an array of TreeNode objects from root to specified node
private Object[] createNodePath(TreeNode node)
{
Object[] retVal;
if (node != null) {
Stack<TreeNode> stack = new Stack<TreeNode>();
while (node != null) {
stack.push(node);
node = node.getParent();
}
retVal = new Object[stack.size()];
for (int i = 0; i < retVal.length; i++) {
retVal[i] = stack.pop();
}
return retVal;
} else {
retVal = new Object[0];
}
return retVal;
}
// Determines the child index based on the specified node's parent
private int getChildNodeIndex(TreeNode node)
{
int retVal = 0;
if (node != null && node.getParent() != null) {
TreeNode parent = node.getParent();
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getChildAt(i) == node) {
retVal = i;
break;
}
}
}
return retVal;
}
private void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices,
Object[] children)
{
if (!listeners.isEmpty()) {
TreeModelEvent event;
if (path == null || path.length == 0) {
event = new TreeModelEvent(source, (TreePath)null);
} else {
event = new TreeModelEvent(source, path, childIndices, children);
}
for (int i = listeners.size()-1; i >= 0; i--) {
TreeModelListener tml = listeners.get(i);
tml.treeNodesChanged(event);
}
}
}
// private void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices,
// Object[] children)
// {
// if (!listeners.isEmpty()) {
// TreeModelEvent event;
// if (path == null || path.length == 0) {
// event = new TreeModelEvent(source, (TreePath)null);
// } else {
// event = new TreeModelEvent(source, path, childIndices, children);
// }
// for (int i = listeners.size()-1; i >= 0; i--) {
// TreeModelListener tml = listeners.get(i);
// tml.treeNodesInserted(event);
// }
// }
// }
// private void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices,
// Object[] children)
// {
// if (!listeners.isEmpty()) {
// TreeModelEvent event;
// if (path == null || path.length == 0) {
// event = new TreeModelEvent(source, (TreePath)null);
// } else {
// event = new TreeModelEvent(source, path, childIndices, children);
// }
// for (int i = listeners.size()-1; i >= 0; i--) {
// TreeModelListener tml = listeners.get(i);
// tml.treeNodesRemoved(event);
// }
// }
// }
private void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices,
Object[] children)
{
if (!listeners.isEmpty()) {
TreeModelEvent event;
if (path == null || path.length == 0) {
event = new TreeModelEvent(source, (TreePath)null);
} else {
event = new TreeModelEvent(source, path, childIndices, children);
}
for (int i = listeners.size()-1; i >= 0; i--) {
TreeModelListener tml = listeners.get(i);
tml.treeStructureChanged(event);
}
}
}
private void initState(StateItem state)
{
if (state != null) {
DlgResource dlg = state.getDialog();
HashMap<Integer, StateItem> map = getStateTable(dlg.getResourceEntry().getResourceName());
if (map == null) {
map = new HashMap<Integer, StateItem>();
setStateTable(dlg.getResourceEntry().getResourceName(), map);
}
if (!map.containsKey(Integer.valueOf(state.getState().getNumber()))) {
map.put(Integer.valueOf(state.getState().getNumber()), state);
for (int i = 0; i < state.getState().getTransCount(); i++) {
int transIdx = state.getState().getFirstTrans() + i;
StructEntry entry = dlg.getAttribute(Transition.DLG_TRANS + " " + transIdx);
if (entry instanceof Transition) {
initTransition(new TransitionItem(dlg, (Transition)entry));
}
}
}
}
}
private void initTransition(TransitionItem trans)
{
if (trans != null) {
DlgResource dlg = trans.getDialog();
HashMap<Integer, TransitionItem> map = getTransitionTable(dlg.getResourceEntry().getResourceName());
if (map == null) {
map = new HashMap<Integer, TransitionItem>();
setTransitionTable(dlg.getResourceEntry().getResourceName(), map);
}
if (!map.containsKey(Integer.valueOf(trans.getTransition().getNumber()))) {
map.put(Integer.valueOf(trans.getTransition().getNumber()), trans);
if (!trans.getTransition().getFlag().isFlagSet(3)) {
// dialog continues
ResourceRef dlgRef = trans.getTransition().getNextDialog();
int stateIdx = trans.getTransition().getNextDialogState();
dlg = getDialogResource(dlgRef.getResourceName());
if (dlg != null && stateIdx >= 0) {
StructEntry entry = dlg.getAttribute(State.DLG_STATE + " " + stateIdx);
if (entry instanceof State) {
initState(new StateItem(dlg, (State)entry));
}
}
}
}
}
}
// Returns a dialog resource object based on the specified resource name
// Reuses exising DlgResource objects if available
private DlgResource getDialogResource(String dlgName)
{
if (dlgName != null) {
if (containsStateTable(dlgName)) {
HashMap<Integer, StateItem> map = getStateTable(dlgName);
if (!map.keySet().isEmpty()) {
return map.get(map.keySet().iterator().next()).getDialog();
}
} else if (containsTransitionTable(dlgName)) {
HashMap<Integer, TransitionItem> map = getTransitionTable(dlgName);
if (!map.keySet().isEmpty()) {
return map.get(map.keySet().iterator().next()).getDialog();
}
} else if (ResourceFactory.resourceExists(dlgName)) {
try {
return new DlgResource(ResourceFactory.getResourceEntry(dlgName));
} catch (Exception e) {
e.printStackTrace();
}
}
}
return null;
}
// Adds all available child nodes to the given parent node
private DefaultMutableTreeNode updateNodeChildren(DefaultMutableTreeNode parent)
{
if (parent != null) {
if (parent.getUserObject() instanceof StateItem) {
return updateStateNodeChildren(parent);
} else if (parent.getUserObject() instanceof TransitionItem) {
return updateTransitionNodeChildren(parent);
} else if (parent.getUserObject() instanceof RootItem) {
return updateRootNodeChildren(parent);
}
}
return parent;
}
// Adds all available transition child nodes to the given parent state node
private DefaultMutableTreeNode updateStateNodeChildren(DefaultMutableTreeNode parent)
{
if (parent != null && parent.getUserObject() instanceof StateItem) {
StateItem state = (StateItem)parent.getUserObject();
String dlgName = state.getDialog().getResourceEntry().getResourceName();
int count = state.getState().getTransCount();
while (parent.getChildCount() < count) {
int transIdx = state.getState().getFirstTrans() + parent.getChildCount();
TransitionItem child = getTransitionTable(dlgName).get(Integer.valueOf(transIdx));
boolean allowChildren = !child.getTransition().getFlag().isFlagSet(3);
DefaultMutableTreeNode nodeChild = new DefaultMutableTreeNode(child, allowChildren);
parent.add(nodeChild);
}
}
return parent;
}
// Adds all available state child nodes to the given parent transition node
private DefaultMutableTreeNode updateTransitionNodeChildren(DefaultMutableTreeNode parent)
{
if (parent != null && parent.getUserObject() instanceof TransitionItem) {
// transitions only allow a single state as child
if (parent.getChildCount() < 1) {
TransitionItem trans = (TransitionItem)parent.getUserObject();
ResourceRef dlgRef = trans.getTransition().getNextDialog();
if (!dlgRef.isEmpty()) {
String dlgName = dlgRef.getResourceName();
int stateIdx = trans.getTransition().getNextDialogState();
StateItem child = getStateTable(dlgName).get(Integer.valueOf(stateIdx));
DefaultMutableTreeNode nodeChild = new DefaultMutableTreeNode(child, true);
parent.add(nodeChild);
}
}
}
return parent;
}
// Adds all available initial state child nodes to the given parent root node
private DefaultMutableTreeNode updateRootNodeChildren(DefaultMutableTreeNode parent)
{
if (parent != null && parent.getUserObject() instanceof RootItem) {
RootItem root = (RootItem)parent.getUserObject();
while (parent.getChildCount() < root.getInitialStateCount()) {
int stateIdx = parent.getChildCount();
StateItem child = root.getInitialState(stateIdx);
DefaultMutableTreeNode nodeChild = new DefaultMutableTreeNode(child, true);
parent.add(nodeChild);
}
}
return parent;
}
// Returns the state table of the specified dialog resource
private HashMap<Integer, StateItem> getStateTable(String dlgName)
{
if (dlgName != null) {
return mapState.get(dlgName.toUpperCase(Locale.ENGLISH));
} else {
return null;
}
}
// Adds or replaces a dialog resource entry with its associated state table
private void setStateTable(String dlgName, HashMap<Integer, StateItem> map)
{
if (dlgName != null) {
mapState.put(dlgName.toUpperCase(Locale.ENGLISH), map);
}
}
// Returns whether the specified dialog resource has been mapped
private boolean containsStateTable(String dlgName)
{
if (dlgName != null) {
return mapState.containsKey(dlgName.toUpperCase(Locale.ENGLISH));
} else {
return false;
}
}
// Returns the transition table of the specified dialog resource
private HashMap<Integer, TransitionItem> getTransitionTable(String dlgName)
{
if (dlgName != null) {
return mapTransition.get(dlgName.toUpperCase(Locale.ENGLISH));
} else {
return null;
}
}
// Adds or replaces a dialog resource entry with its associated transition table
private void setTransitionTable(String dlgName, HashMap<Integer, TransitionItem> map)
{
if (dlgName != null) {
mapTransition.put(dlgName.toUpperCase(Locale.ENGLISH), map);
}
}
// Returns whether the specified dialog resource has been mapped
private boolean containsTransitionTable(String dlgName)
{
if (dlgName != null) {
return mapTransition.containsKey(dlgName.toUpperCase(Locale.ENGLISH));
} else {
return false;
}
}
}
// Panel for displaying information about the current dialog state or trigger
private static final class ItemInfo extends JPanel
{
/** Identifies the respective controls for displaying information. */
private enum Type {
STATE, STATE_TEXT, STATE_WAV, STATE_TRIGGER,
RESPONSE, RESPONSE_FLAGS, RESPONSE_TEXT, RESPONSE_JOURNAL, RESPONSE_TRIGGER, RESPONSE_ACTION
}
private static final String CARD_EMPTY = "Empty";
private static final String CARD_STATE = "State";
private static final String CARD_RESPONSE = "Response";
private static final Color COLOR_BACKGROUND = UIManager.getColor("Panel.background");
private static final Font FONT_DEFAULT = UIManager.getFont("Label.font").deriveFont(0);
private final CardLayout cardLayout;
private final JPanel pMainPanel, pState, pResponse, pStateText, pStateWAV, pStateTrigger,
pResponseFlags, pResponseText, pResponseJournal, pResponseTrigger,
pResponseAction;
private final ScriptTextArea taStateTrigger, taResponseTrigger, taResponseAction;
private final LinkButton lbStateWAV;
private final JTextArea taStateText, taResponseText, taResponseJournal;
private final JTextField tfResponseFlags;
public ItemInfo()
{
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
cardLayout = new CardLayout(0, 0);
pMainPanel = new JPanel(cardLayout);
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.BOTH, new Insets(8, 8, 8, 8), 0, 0);
add(pMainPanel, gbc);
// shown when no item has been selected
pMainPanel.add(new JPanel(), CARD_EMPTY);
// initializing state item info
pState = new JPanel(new GridBagLayout());
pState.setBorder(createTitledBorder("State", Font.BOLD, true));
pMainPanel.add(pState, CARD_STATE);
taStateText = createReadOnlyTextArea();
taStateText.setMargin(new Insets(0, 4, 0, 4));
pStateText = new JPanel(new BorderLayout());
pStateText.setBorder(createTitledBorder("Associated text", Font.BOLD, false));
pStateText.add(taStateText, BorderLayout.CENTER);
lbStateWAV = new LinkButton("");
pStateWAV = new JPanel(new GridBagLayout());
pStateWAV.setBorder(createTitledBorder("Sound Resource", Font.BOLD, false));
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.BOTH, new Insets(0, 4, 0, 4), 0, 0);
pStateWAV.add(lbStateWAV, gbc);
taStateTrigger = createScriptTextArea(true);
taStateTrigger.setFont(BrowserMenuBar.getInstance().getScriptFont());
taStateTrigger.setMargin(new Insets(0, 4, 0, 4));
pStateTrigger = new JPanel(new BorderLayout());
pStateTrigger.setBorder(createTitledBorder("State trigger", Font.BOLD, false));
pStateTrigger.add(taStateTrigger, BorderLayout.CENTER);
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0);
pState.add(pStateText, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0);
pState.add(pStateWAV, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0);
pState.add(pStateTrigger, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 3, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0);
pState.add(new JPanel(), gbc);
// initializing response item info
pResponse = new JPanel(new GridBagLayout());
pResponse.setBorder(createTitledBorder("Response", Font.BOLD, true));
pMainPanel.add(pResponse, CARD_RESPONSE);
tfResponseFlags = createReadOnlyTextField();
tfResponseFlags.setMargin(new Insets(0, 4, 0, 4));
pResponseFlags = new JPanel(new BorderLayout());
pResponseFlags.setBorder(createTitledBorder("Flags", Font.BOLD, false));
pResponseFlags.add(tfResponseFlags, BorderLayout.CENTER);
taResponseText = createReadOnlyTextArea();
taResponseText.setMargin(new Insets(0, 4, 0, 4));
pResponseText = new JPanel(new BorderLayout());
pResponseText.setBorder(createTitledBorder("Associated text", Font.BOLD, false));
pResponseText.add(taResponseText, BorderLayout.CENTER);
taResponseJournal = createReadOnlyTextArea();
taResponseJournal.setMargin(new Insets(0, 4, 0, 4));
pResponseJournal = new JPanel(new BorderLayout());
pResponseJournal.setBorder(createTitledBorder("Journal entry", Font.BOLD, false));
pResponseJournal.add(taResponseJournal, BorderLayout.CENTER);
taResponseTrigger = createScriptTextArea(true);
taResponseTrigger.setFont(BrowserMenuBar.getInstance().getScriptFont());
taResponseTrigger.setMargin(new Insets(0, 4, 0, 4));
pResponseTrigger = new JPanel(new BorderLayout());
pResponseTrigger.setBorder(createTitledBorder("Response trigger", Font.BOLD, false));
pResponseTrigger.add(taResponseTrigger, BorderLayout.CENTER);
taResponseAction = createScriptTextArea(true);
taResponseAction.setFont(BrowserMenuBar.getInstance().getScriptFont());
taResponseAction.setMargin(new Insets(0, 4, 0, 4));
pResponseAction = new JPanel(new BorderLayout());
pResponseAction.setBorder(createTitledBorder("Action", Font.BOLD, false));
pResponseAction.add(taResponseAction, BorderLayout.CENTER);
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0);
pResponse.add(pResponseFlags, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0);
pResponse.add(pResponseText, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0);
pResponse.add(pResponseJournal, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0);
pResponse.add(pResponseTrigger, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 4, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0);
pResponse.add(pResponseAction, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 5, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0);
pResponse.add(new JPanel(), gbc);
showPanel(CARD_EMPTY);
}
/** Shows the panel of given name. */
public void showPanel(String cardName)
{
if (cardName != null) {
if (cardName.equals(CARD_STATE)) {
clearCard(CARD_RESPONSE);
} else if (cardName.equals(CARD_RESPONSE)) {
clearCard(CARD_STATE);
} else {
cardName = CARD_EMPTY;
clearCard(CARD_STATE);
clearCard(CARD_RESPONSE);
}
cardLayout.show(pMainPanel, cardName);
}
}
/** Enable/disable visibility of the given control. */
public void showControl(Type type, boolean b)
{
getControl(type).setVisible(b);
}
/** Update border title of the given control. */
public void updateControlBorder(Type type, String title)
{
if (title == null) title = "";
if (getControl(type).getBorder() instanceof TitledBorder) {
TitledBorder b = (TitledBorder)getControl(type).getBorder();
b.setTitle(" " + title + " ");
getControl(type).repaint();
}
}
/** Update content of the given control. */
public void updateControlText(Type type, String text)
{
if (text == null) text = "";
switch (type) {
case STATE_TEXT: taStateText.setText(text); break;
case STATE_WAV: lbStateWAV.setResource(text); break;
case STATE_TRIGGER: taStateTrigger.setText(text); break;
case RESPONSE_FLAGS: tfResponseFlags.setText(text); break;
case RESPONSE_TEXT: taResponseText.setText(text); break;
case RESPONSE_JOURNAL: taResponseJournal.setText(text); break;
case RESPONSE_TRIGGER: taResponseTrigger.setText(text); break;
case RESPONSE_ACTION: taResponseAction.setText(text); break;
default:
}
}
// Returns the given control
private JPanel getControl(Type type)
{
switch (type) {
case STATE: return pState;
case RESPONSE: return pResponse;
case STATE_TEXT: return pStateText;
case STATE_WAV: return pStateWAV;
case STATE_TRIGGER: return pStateTrigger;
case RESPONSE_FLAGS: return pResponseFlags;
case RESPONSE_TEXT: return pResponseText;
case RESPONSE_JOURNAL: return pResponseJournal;
case RESPONSE_TRIGGER: return pResponseTrigger;
case RESPONSE_ACTION: return pResponseAction;
}
return new JPanel();
}
// Clears and disables controls in the specified panel
private void clearCard(String cardName)
{
if (cardName != null) {
if (cardName.equals(CARD_STATE)) {
updateControlText(Type.STATE_TEXT, "");
showControl(Type.STATE_TEXT, false);
updateControlText(Type.STATE_WAV, "");
showControl(Type.STATE_WAV, false);
updateControlText(Type.STATE_TRIGGER, "");
showControl(Type.STATE_TRIGGER, false);
} else if (cardName.equals(CARD_RESPONSE)) {
updateControlText(Type.RESPONSE_FLAGS, "");
showControl(Type.RESPONSE_FLAGS, false);
updateControlText(Type.RESPONSE_TEXT, "");
showControl(Type.RESPONSE_TEXT, false);
updateControlText(Type.RESPONSE_JOURNAL, "");
showControl(Type.RESPONSE_JOURNAL, false);
updateControlText(Type.RESPONSE_TRIGGER, "");
showControl(Type.RESPONSE_TRIGGER, false);
updateControlText(Type.RESPONSE_ACTION, "");
showControl(Type.RESPONSE_ACTION, false);
}
}
}
// Helper method for creating a read-only textarea component
private JTextArea createReadOnlyTextArea()
{
JTextArea ta = new JTextArea();
ta.setEditable(false);
ta.setFont(FONT_DEFAULT);
ta.setBackground(COLOR_BACKGROUND);
ta.setWrapStyleWord(true);
ta.setLineWrap(true);
return ta;
}
// Helper method for creating a ScriptTextArea component
private ScriptTextArea createScriptTextArea(boolean readOnly)
{
ScriptTextArea ta = new ScriptTextArea();
if (readOnly) {
ta.setBackground(COLOR_BACKGROUND);
ta.setHighlightCurrentLine(false);
}
ta.setEditable(!readOnly);
ta.setWrapStyleWord(true);
ta.setLineWrap(true);
return ta;
}
// Helper method for creating a read-only textfield component
private JTextField createReadOnlyTextField()
{
JTextField tf = new JTextField();
tf.setBorder(BorderFactory.createEmptyBorder());
tf.setEditable(false);
tf.setFont(FONT_DEFAULT);
tf.setBackground(COLOR_BACKGROUND);
return tf;
}
// Returns a modified TitledBorder object
private TitledBorder createTitledBorder(String title, int fontStyle, boolean isTitle)
{
if(title == null) title = "";
TitledBorder tb = BorderFactory.createTitledBorder(title);
Font f = tb.getTitleFont();
if (f == null) {
f = FONT_DEFAULT;
}
if (f != null) {
tb.setTitleFont(new Font(f.getFamily(), fontStyle, isTitle ? (f.getSize() + 1) : f.getSize()));
}
return tb;
}
}
}