// 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.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import org.infinity.NearInfinity;
import org.infinity.datatype.ResourceRef;
import org.infinity.datatype.StringRef;
import org.infinity.gui.BrowserMenuBar;
import org.infinity.gui.ButtonPanel;
import org.infinity.gui.ButtonPopupMenu;
import org.infinity.gui.InfinityScrollPane;
import org.infinity.gui.ScriptTextArea;
import org.infinity.gui.ViewFrame;
import org.infinity.icon.Icons;
import org.infinity.resource.AbstractStruct;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.StructEntry;
import org.infinity.resource.bcs.Compiler;
import org.infinity.resource.bcs.Decompiler;
import org.infinity.resource.key.ResourceEntry;
import org.infinity.search.DialogSearcher;
import org.infinity.util.StringResource;
final class Viewer extends JPanel implements ActionListener, ItemListener, TableModelListener
{
private static final ButtonPanel.Control CtrlNextState = ButtonPanel.Control.CUSTOM_1;
private static final ButtonPanel.Control CtrlPrevState = ButtonPanel.Control.CUSTOM_2;
private static final ButtonPanel.Control CtrlNextTrans = ButtonPanel.Control.CUSTOM_3;
private static final ButtonPanel.Control CtrlPrevTrans = ButtonPanel.Control.CUSTOM_4;
private static final ButtonPanel.Control CtrlSelect = ButtonPanel.Control.CUSTOM_5;
private static final ButtonPanel.Control CtrlUndo = ButtonPanel.Control.CUSTOM_6;
private static final ButtonPanel.Control CtrlStateField = ButtonPanel.Control.CUSTOM_7;
private static final ButtonPanel.Control CtrlResponseField = ButtonPanel.Control.CUSTOM_8;
private final ButtonPanel buttonPanel = new ButtonPanel();
private final DlgPanel stateTextPanel, stateTriggerPanel, transTextPanel, transTriggerPanel, transActionPanel;
private final DlgResource dlg;
private final JMenuItem ifindall = new JMenuItem("in all DLG files");
private final JMenuItem ifindthis = new JMenuItem("in this file only");
private final JPanel outerpanel;
private final JTextField tfState = new JTextField(4);
private final JTextField tfResponse = new JTextField(4);
private final List<Action> actionList = new ArrayList<Action>();
private final List<ResponseTrigger> transTriList = new ArrayList<ResponseTrigger>();
private final List<State> stateList = new ArrayList<State>();
private final List<StateTrigger> staTriList = new ArrayList<StateTrigger>();
private final List<Transition> transList = new ArrayList<Transition>();
private final Stack<State> lastStates = new Stack<State>();
private final Stack<Transition> lastTransitions = new Stack<Transition>();
private final TitledBorder bostate = new TitledBorder("State");
private final TitledBorder botrans = new TitledBorder("Response");
private State currentstate;
private Transition currenttransition;
private boolean alive = true;
private DlgResource undoDlg;
Viewer(DlgResource dlg)
{
this.dlg = dlg;
this.dlg.addTableModelListener(this);
ButtonPopupMenu bpmFind = (ButtonPopupMenu)ButtonPanel.createControl(ButtonPanel.Control.FIND_MENU);
bpmFind.setMenuItems(new JMenuItem[]{ifindall, ifindthis});
bpmFind.addItemListener(this);
bpmFind.addActionListener(this);
JButton bNextState = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16));
bNextState.setMargin(new Insets(bNextState.getMargin().top, 0, bNextState.getMargin().bottom, 0));
bNextState.addActionListener(this);
JButton bPrevState = new JButton(Icons.getIcon(Icons.ICON_BACK_16));
bPrevState.setMargin(bNextState.getMargin());
bPrevState.addActionListener(this);
JButton bNextTrans = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16));
bNextTrans.setMargin(bNextState.getMargin());
bNextTrans.addActionListener(this);
JButton bPrevTrans = new JButton(Icons.getIcon(Icons.ICON_BACK_16));
bPrevTrans.setMargin(bNextState.getMargin());
bPrevTrans.addActionListener(this);
JButton bSelect = new JButton("Select", Icons.getIcon(Icons.ICON_REDO_16));
bSelect.addActionListener(this);
JButton bUndo = new JButton("Undo", Icons.getIcon(Icons.ICON_UNDO_16));
bUndo.addActionListener(this);
int width = (int)tfState.getPreferredSize().getWidth();
int height = (int)bNextState.getPreferredSize().getHeight();
tfState.setPreferredSize(new Dimension(width, height));
tfResponse.setPreferredSize(new Dimension(width, height));
tfState.setHorizontalAlignment(JTextField.CENTER);
tfResponse.setHorizontalAlignment(JTextField.CENTER);
tfState.addActionListener(this);
tfResponse.addActionListener(this);
stateTextPanel = new DlgPanel("Text", true);
stateTriggerPanel = new DlgPanel("Trigger", false, true);
transTextPanel = new DlgPanel("Text", true);
transTriggerPanel = new DlgPanel("Trigger", false, true);
transActionPanel = new DlgPanel("Action", false, true);
JPanel statepanel = new JPanel();
statepanel.setLayout(new GridLayout(2, 1, 6, 6));
statepanel.add(stateTextPanel);
statepanel.add(stateTriggerPanel);
statepanel.setBorder(bostate);
JPanel transpanel2 = new JPanel();
transpanel2.setLayout(new GridLayout(1, 2, 6, 6));
transpanel2.add(transTriggerPanel);
transpanel2.add(transActionPanel);
JPanel transpanel = new JPanel();
transpanel.setLayout(new GridLayout(2, 1, 6, 6));
transpanel.add(transTextPanel);
transpanel.add(transpanel2);
transpanel.setBorder(botrans);
outerpanel = new JPanel();
outerpanel.setLayout(new GridLayout(2, 1, 6, 6));
outerpanel.add(statepanel);
outerpanel.add(transpanel);
buttonPanel.addControl(new JLabel("State:"));
buttonPanel.addControl(tfState, CtrlStateField);
buttonPanel.addControl(bPrevState, CtrlPrevState);
buttonPanel.addControl(bNextState, CtrlNextState);
buttonPanel.addControl(new JLabel(" Response:"));
buttonPanel.addControl(tfResponse, CtrlResponseField);
buttonPanel.addControl(bPrevTrans, CtrlPrevTrans);
buttonPanel.addControl(bNextTrans, CtrlNextTrans);
buttonPanel.addControl(bSelect, CtrlSelect);
buttonPanel.addControl(bUndo, CtrlUndo);
buttonPanel.addControl(bpmFind, ButtonPanel.Control.FIND_MENU);
setLayout(new BorderLayout());
add(outerpanel, BorderLayout.CENTER);
add(buttonPanel, BorderLayout.SOUTH);
outerpanel.setBorder(BorderFactory.createLoweredBevelBorder());
updateViewerLists();
if (stateList.size() > 0) {
showState(0);
showTransition(currentstate.getFirstTrans());
} else {
bPrevState.setEnabled(false);
bNextState.setEnabled(false);
bPrevTrans.setEnabled(false);
bNextTrans.setEnabled(false);
bSelect.setEnabled(false);
}
bUndo.setEnabled(false);
}
public void setUndoDlg(DlgResource dlg)
{
this.undoDlg = dlg;
buttonPanel.getControlByType(CtrlUndo).setEnabled(true);
}
// --------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent event)
{
if (!alive) return;
if (buttonPanel.getControlByType(CtrlUndo) == event.getSource()) {
JButton bUndo = (JButton)event.getSource();
if(lastStates.empty() && (undoDlg != null)) {
showExternState(undoDlg, -1, true);
return;
}
State oldstate = lastStates.pop();
Transition oldtrans = lastTransitions.pop();
if (lastStates.empty() && (undoDlg == null)) {
bUndo.setEnabled(false);
}
if (oldstate != currentstate) {
showState(oldstate.getNumber());
}
if (oldtrans != currenttransition) {
showTransition(oldtrans.getNumber());
}
} else {
int newstate = currentstate.getNumber();
int newtrans = currenttransition.getNumber();
if (buttonPanel.getControlByType(CtrlNextState) == event.getSource()) {
newstate++;
} else if (buttonPanel.getControlByType(CtrlPrevState) == event.getSource()) {
newstate--;
} else if (buttonPanel.getControlByType(CtrlNextTrans) == event.getSource()) {
newtrans++;
} else if (buttonPanel.getControlByType(CtrlPrevTrans) == event.getSource()) {
newtrans--;
} else if (event.getSource() == tfState) {
try {
int number = Integer.parseInt(tfState.getText());
if (number >= 0 && number <= stateList.size()) {
newstate = number;
} else {
tfState.setText(String.valueOf(currentstate.getNumber()));
}
} catch (Exception e) {
tfState.setText(String.valueOf(currentstate.getNumber()));
}
} else if (event.getSource() == tfResponse) {
try {
int number = Integer.parseInt(tfResponse.getText());
if (number >= 0 && number <= currentstate.getTransCount()) {
newtrans = currentstate.getFirstTrans() + number;
} else {
tfResponse.setText(String.valueOf(currenttransition.getNumber() - currentstate.getFirstTrans()));
}
} catch (Exception e) {
tfResponse.setText(String.valueOf(currenttransition.getNumber() - currentstate.getFirstTrans()));
}
} else if (buttonPanel.getControlByType(CtrlSelect) == event.getSource()) {
ResourceRef next_dlg = currenttransition.getNextDialog();
if (dlg.getResourceEntry().toString().equalsIgnoreCase(next_dlg.toString())) {
lastStates.push(currentstate);
lastTransitions.push(currenttransition);
buttonPanel.getControlByType(CtrlUndo).setEnabled(true);
newstate = currenttransition.getNextDialogState();
} else {
DlgResource newdlg =
(DlgResource)ResourceFactory.getResource(ResourceFactory.getResourceEntry(next_dlg.toString()));
showExternState(newdlg, currenttransition.getNextDialogState(), false);
}
}
if (alive) {
if (newstate != currentstate.getNumber()) {
showState(newstate);
showTransition(stateList.get(newstate).getFirstTrans());
} else if (newtrans != currenttransition.getNumber()) {
showTransition(newtrans);
}
}
}
}
// --------------------- End Interface ActionListener ---------------------
// --------------------- Begin Interface ItemListener ---------------------
@Override
public void itemStateChanged(ItemEvent event)
{
if (buttonPanel.getControlByType(ButtonPanel.Control.FIND_MENU) == event.getSource()) {
ButtonPopupMenu bpmFind = (ButtonPopupMenu)event.getSource();
if (bpmFind.getSelectedItem() == ifindall) {
List<ResourceEntry> files = ResourceFactory.getResources("DLG");
new DialogSearcher(files, getTopLevelAncestor());
} else if (bpmFind.getSelectedItem() == ifindthis) {
List<ResourceEntry> files = new ArrayList<ResourceEntry>();
files.add(dlg.getResourceEntry());
new DialogSearcher(files, getTopLevelAncestor());
}
}
}
// --------------------- End Interface ItemListener ---------------------
// --------------------- Begin Interface TableModelListener ---------------------
@Override
public void tableChanged(TableModelEvent e)
{
updateViewerLists();
showState(currentstate.getNumber());
showTransition(currenttransition.getNumber());
}
// --------------------- End Interface TableModelListener ---------------------
// for quickly jump to the corresponding state while only having a StructEntry
public void showStateWithStructEntry(StructEntry entry)
{
int stateNrToShow = 0;
int transNrToShow = 0;
// we can have states, triggers, transitions and actions
if (entry instanceof State) {
stateNrToShow = ((State) entry).getNumber();
transNrToShow = ((State) entry).getFirstTrans();
}
else if (entry instanceof Transition) {
int transnr = ((Transition) entry).getNumber();
stateNrToShow = findStateForTrans(transnr);
transNrToShow = transnr;
}
else if (entry instanceof StateTrigger) {
int triggerOffset = ((StateTrigger) entry).getOffset();
int nr = 0;
for (StateTrigger trig : staTriList) {
if (trig.getOffset() == triggerOffset) {
break;
}
nr++;
}
for (State state : stateList) {
if (state.getTriggerIndex() == nr) {
stateNrToShow = state.getNumber();
transNrToShow = state.getFirstTrans();
break;
}
}
}
else if (entry instanceof ResponseTrigger) {
int triggerOffset = ((ResponseTrigger) entry).getOffset();
int nr = 0;
for (ResponseTrigger trig : transTriList) {
if (trig.getOffset() == triggerOffset) {
break;
}
nr++;
}
for (Transition trans : transList) {
if (trans.getTriggerIndex() == nr) {
transNrToShow = trans.getNumber();
stateNrToShow = findStateForTrans(transNrToShow);
}
}
}
else if (entry instanceof Action) {
int actionOffset = ((Action) entry).getOffset();
int nr = 0;
for (Action action : actionList) {
if (action.getOffset() == actionOffset) {
break;
}
nr++;
}
for (Transition trans : transList) {
if (trans.getActionIndex() == nr) {
transNrToShow = trans.getNumber();
stateNrToShow = findStateForTrans(transNrToShow);
}
}
}
else if (entry instanceof StringRef) {
// this can happen with the dlg search
// check all states and transitions
int strref = ((StringRef) entry).getValue();
boolean found = false;
for (State state : stateList) {
if (state.getResponse().getValue() == strref) {
stateNrToShow = state.getNumber();
transNrToShow = state.getFirstTrans();
found = true;
break;
}
}
if (!found) {
for (Transition trans : transList) {
if (trans.getAssociatedText().getValue() == strref) {
transNrToShow = trans.getNumber();
stateNrToShow = findStateForTrans(transNrToShow);
break;
}
else if (trans.getJournalEntry().getValue() == strref) {
transNrToShow = trans.getNumber();
stateNrToShow = findStateForTrans(transNrToShow);
break;
}
}
}
}
showState(stateNrToShow);
showTransition(transNrToShow);
}
private int findStateForTrans(int transnr)
{
for (State state : stateList) {
if ((transnr >= state.getFirstTrans()) &&
(transnr < (state.getFirstTrans() + state.getTransCount()))) {
return state.getNumber();
}
}
// default
return 0;
}
private void showState(int nr)
{
if (currentstate != null) {
currentstate.removeTableModelListener(this);
}
currentstate = stateList.get(nr);
currentstate.addTableModelListener(this);
bostate.setTitle("State " + nr + '/' + (stateList.size() - 1));
stateTextPanel.display(currentstate, nr);
tfState.setText(String.valueOf(nr));
outerpanel.repaint();
if (currentstate.getTriggerIndex() != 0xffffffff) {
stateTriggerPanel.display(staTriList.get(currentstate.getTriggerIndex()),
currentstate.getTriggerIndex());
} else {
stateTriggerPanel.clearDisplay();
}
buttonPanel.getControlByType(CtrlPrevState).setEnabled(nr > 0);
buttonPanel.getControlByType(CtrlNextState).setEnabled(nr + 1 < stateList.size());
}
private void showTransition(int nr)
{
if (currenttransition != null) {
currenttransition.removeTableModelListener(this);
}
currenttransition = transList.get(nr);
currenttransition.addTableModelListener(this);
botrans.setTitle("Response " + (nr - currentstate.getFirstTrans()) +
'/' + (currentstate.getTransCount() - 1));
tfResponse.setText(String.valueOf(nr - currentstate.getFirstTrans()));
outerpanel.repaint();
transTextPanel.display(currenttransition, nr);
if (currenttransition.getFlag().isFlagSet(1)) {
transTriggerPanel.display(transTriList.get(currenttransition.getTriggerIndex()),
currenttransition.getTriggerIndex());
} else {
transTriggerPanel.clearDisplay();
}
if (currenttransition.getFlag().isFlagSet(2)) {
transActionPanel.display(actionList.get(currenttransition.getActionIndex()),
currenttransition.getActionIndex());
} else {
transActionPanel.clearDisplay();
}
buttonPanel.getControlByType(CtrlSelect).setEnabled(!currenttransition.getFlag().isFlagSet(3));
buttonPanel.getControlByType(CtrlPrevTrans).setEnabled(nr > currentstate.getFirstTrans());
buttonPanel.getControlByType(CtrlNextTrans)
.setEnabled(nr - currentstate.getFirstTrans() + 1 < currentstate.getTransCount());
}
private void updateViewerLists()
{
stateList.clear();
transList.clear();
staTriList.clear();
transTriList.clear();
actionList.clear();
for (int i = 0; i < dlg.getFieldCount(); i++) {
StructEntry entry = dlg.getField(i);
if (entry instanceof State) {
stateList.add((State)entry);
} else if (entry instanceof Transition) {
transList.add((Transition)entry);
} else if (entry instanceof StateTrigger) {
staTriList.add((StateTrigger)entry);
} else if (entry instanceof ResponseTrigger) {
transTriList.add((ResponseTrigger)entry);
} else if (entry instanceof Action) {
actionList.add((Action)entry);
}
}
}
private void showExternState(DlgResource newdlg, int state, boolean isUndo) {
alive = false;
Container window = getTopLevelAncestor();
if (window instanceof ViewFrame && window.isVisible()) {
((ViewFrame) window).setViewable(newdlg);
} else {
NearInfinity.getInstance().setViewable(newdlg);
}
Viewer newdlg_viewer = (Viewer)newdlg.getViewerTab(0);
if (isUndo) {
newdlg_viewer.alive = true;
newdlg_viewer.repaint(); // only necessary when dlg is in extra window
} else {
newdlg_viewer.setUndoDlg(this.dlg);
newdlg_viewer.showState(state);
newdlg_viewer.showTransition(newdlg_viewer.currentstate.getFirstTrans());
}
// make sure the viewer tab is selected
JTabbedPane parent = (JTabbedPane) newdlg_viewer.getParent();
parent.getModel().setSelectedIndex(parent.indexOfComponent(newdlg_viewer));
}
// -------------------------- INNER CLASSES --------------------------
private final class DlgPanel extends JPanel implements ActionListener
{
private final JButton bView = new JButton(Icons.getIcon(Icons.ICON_ZOOM_16));
private final JButton bGoto = new JButton(Icons.getIcon(Icons.ICON_ROW_INSERT_AFTER_16));
private final JButton bPlay = new JButton(Icons.getIcon(Icons.ICON_VOLUME_16));
private final ScriptTextArea textArea = new ScriptTextArea();
private final JLabel label = new JLabel();
private final String title;
private AbstractStruct struct;
private StructEntry structEntry;
private DlgPanel(String title, boolean viewable)
{
this(title, viewable, false);
}
private DlgPanel(String title, boolean viewable, boolean useHighlighting)
{
this.title = title;
bView.setMargin(new Insets(0, 0, 0, 0));
bView.addActionListener(this);
bGoto.setMargin(bView.getMargin());
bGoto.addActionListener(this);
bPlay.setMargin(bView.getMargin());
bPlay.addActionListener(this);
bView.setToolTipText("View/Edit");
bGoto.setToolTipText("Select attribute");
bPlay.setToolTipText("Open associated sound");
if (!useHighlighting) {
textArea.applyExtendedSettings(null, null);
}
textArea.setEditable(false);
textArea.setHighlightCurrentLine(false);
if (viewable) {
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
}
textArea.setMargin(new Insets(3, 3, 3, 3));
textArea.setFont(BrowserMenuBar.getInstance().getScriptFont());
InfinityScrollPane scroll = new InfinityScrollPane(textArea, true);
if (!useHighlighting) {
scroll.setLineNumbersEnabled(false);
}
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints gbc = new GridBagConstraints();
setLayout(gbl);
gbc.insets = new Insets(0, 3, 0, 0);
gbc.fill = GridBagConstraints.NONE;
gbc.weightx = 0.0;
gbc.weighty = 0.0;
gbc.anchor = GridBagConstraints.WEST;
gbl.setConstraints(bGoto, gbc);
add(bGoto);
if (viewable) {
gbl.setConstraints(bView, gbc);
add(bView);
gbl.setConstraints(bPlay, gbc);
add(bPlay);
}
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets.right = 3;
gbl.setConstraints(label, gbc);
add(label);
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbl.setConstraints(scroll, gbc);
add(scroll);
}
private void display(State state, int number)
{
label.setText(title + " (" + number + ')');
bView.setEnabled(true);
bGoto.setEnabled(true);
struct = state;
structEntry = state;
StringRef response = state.getResponse();
textArea.setText(response.toString() + "\n(StrRef: " + response.getValue() + ')');
bPlay.setEnabled(StringResource.getWavResource(response.getValue()) != null);
textArea.setCaretPosition(0);
}
private void display(Transition trans, int number)
{
label.setText(title + " (" + number + ')');
bView.setEnabled(true);
bGoto.setEnabled(true);
struct = trans;
structEntry = trans;
StringRef assText = trans.getAssociatedText();
StringRef jouText = trans.getJournalEntry();
String text = "";
if (trans.getFlag().isFlagSet(0))
text = assText.toString() + "\n(StrRef: " + assText.getValue() + ")\n";
if (trans.getFlag().isFlagSet(4))
text += "\nJournal entry:\n" + jouText.toString() + "\n(StrRef: " + jouText.getValue() + ')';
bPlay.setEnabled(StringResource.getWavResource(assText.getValue()) != null);
textArea.setText(text);
textArea.setCaretPosition(0);
}
private void display(AbstractCode trigger, int number)
{
label.setText(title + " (" + number + ')');
bView.setEnabled(false);
bPlay.setEnabled(false);
bGoto.setEnabled(true);
structEntry = trigger;
Compiler compiler = new Compiler(trigger.toString(),
(trigger instanceof Action) ? Compiler.ScriptType.ACTION :
Compiler.ScriptType.TRIGGER);
String code = compiler.getCode();
try {
if (compiler.getErrors().size() == 0) {
Decompiler decompiler = new Decompiler(code, true);
if (trigger instanceof Action) {
decompiler.setScriptType(Decompiler.ScriptType.ACTION);
} else {
decompiler.setScriptType(Decompiler.ScriptType.TRIGGER);
}
textArea.setText(decompiler.getSource());
} else {
textArea.setText(trigger.toString());
}
} catch (Exception e) {
textArea.setText(trigger.toString());
}
textArea.setCaretPosition(0);
}
private void clearDisplay()
{
label.setText(title + " (-)");
textArea.setText("");
bView.setEnabled(false);
bGoto.setEnabled(false);
struct = null;
structEntry = null;
}
@Override
public void actionPerformed(ActionEvent event)
{
if (event.getSource() == bView) {
new ViewFrame(getTopLevelAncestor(), struct);
} else if (event.getSource() == bGoto) {
dlg.getViewer().selectEntry(structEntry.getName());
} else if (event.getSource() == bPlay) {
StringRef text = null;
if (struct instanceof State) {
text = ((State)struct).getResponse();
} else if (struct instanceof Transition) {
text = ((Transition)struct).getAssociatedText();
}
if (text != null) {
String resourceName = StringResource.getWavResource(text.getValue());
if (resourceName != null) {
ResourceEntry entry = ResourceFactory.getResourceEntry(resourceName + ".WAV");
new ViewFrame(getTopLevelAncestor(), ResourceFactory.getResource(entry));
}
}
}
}
}
}