package aima.gui.framework;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
/**
* <p>
* Universal frame for building graphical agent applications. It provides
* buttons for controlling the application and two panels and a status bar to
* visualize agent, environment, and general simulation state.
* </p>
* <p>
* To make the frame fit to your needs, you will at least have to add some
* selectors. The frame is configurable at run-time, so subclassing
* will not always be necessary.
* </p>
*
* @author Ruediger Lunde
*/
public class AgentAppFrame extends JFrame {
private static final long serialVersionUID = 1L;
/** The controller, which executes the domain-level commands. */
protected AgentAppController controller;
/** Background thread for simulation. */
private SimulationThread simulationThread;
/** Contains selector specification and resulting comboboxes. */
private SelectorContainer selectors;
/**
* Listens to all button events and delegates most of the work to the
* controller.
*/
protected FrameActionListener actionListener;
private JToolBar toolbar;
private JButton clearButton;
private JButton prepareButton;
private JButton runButton;
protected JButton stepButton;
private JToggleButton pauseButton;
private JButton cancelButton;
private JLabel statusLabel;
protected JSplitPane centerPane;
private MessageLoggerPanel messageLogger;
private AgentAppEnvironmentView envView;
/** Standard constructor. */
public AgentAppFrame() {
actionListener = new FrameActionListener();
initComponents();
pack();
// redirect the standard output into the text area
System.setOut(messageLogger.getPrintStream());
// System.setErr(messageLogger.getPrintStream());
updateEnabledState();
}
/** Returns a logger which displays messages in a message log pane. */
public MessageLoggerPanel getMessageLogger() {
return messageLogger;
}
/**
* Specifies a set of combo boxes to be added to the toolbar. Each combobox
* has a name, which is used to access its selection state on software level
* and optionally a tool tip, which is shown to the user.
*
* @param tooltips
* Array of strings or null.
*
*/
public void setSelectors(String[] selectorNames, String[] tooltips) {
AgentAppController cont = controller;
controller = null; // suppress reactions on parameter changes.
selectors.setSelectors(selectorNames, tooltips);
controller = cont;
}
/**
* Sets the choice items and the default value of a specified selector. The
* first item has index 0.
*/
public void setSelectorItems(String selectorName, String[] items,
int defaultIdx) {
AgentAppController cont = controller;
controller = null; // suppress reactions on parameter changes.
selectors.setSelectorItems(selectorName, items, defaultIdx);
controller = cont;
}
/** Adjusts selection state of all selectors. */
public void setDefaultSelection() {
AgentAppController cont = controller;
controller = null; // suppress reactions on parameter changes.
selectors.setDefaults();
if (cont != null) {
controller = cont;
selectionChanged(null);
}
}
/**
* Returns an object which represents the current selection state of all
* selectors.
*/
public SelectionState getSelection() {
return selectors.getSelection();
}
/**
* Returns the environment view which is currently used to display
* the agents in their environment.
*/
public AgentAppEnvironmentView getEnvView() {
return envView;
}
/**
* Replaces the environment view. The environment view is the
* panel to the left of the splitbar. It typically implements a
* 2D-visualization of agents in their environment.
*/
public void setEnvView(AgentAppEnvironmentView view) {
envView = view;
centerPane.add(JSplitPane.LEFT, envView);
envView.setController(controller);
}
/** Specifies how to distribute extra space when resizing the split pane. */
public void setSplitPaneResizeWeight(double value) {
centerPane.setResizeWeight(value);
}
/** Defines, who should react on button and selection events. */
public void setController(AgentAppController controller) {
this.controller = controller;
if (envView != null)
envView.setController(controller);
}
/** Displays a text in the status bar. */
public void setStatus(String status) {
statusLabel.setText(status);
}
/** Returns the current simulation thread or null (no simulation running). */
protected SimulationThread getSimulationThread() {
return simulationThread;
}
/**
* Sets the current agent thread and updates the enabled state.
* @param thread A thread or null.
*/
protected void setSimulationThread(SimulationThread thread) {
simulationThread = thread;
updateEnabledState();
}
/** Returns true if simulation pause button was pressed. */
public boolean simulationPaused() {
return pauseButton.isSelected();
}
/** Assembles the inner structure of the frame. */
private void initComponents() {
addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosing(java.awt.event.WindowEvent evt) {
System.exit(0);
}
});
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new BorderLayout());
getContentPane().add(contentPanel, BorderLayout.CENTER);
toolbar = new JToolBar();
toolbar.setFloatable(false);
selectors = new SelectorContainer();
toolbar.add(Box.createHorizontalGlue());
clearButton = new JButton("Clear");
clearButton.setToolTipText("Clear Views");
clearButton.addActionListener(actionListener);
toolbar.add(clearButton);
prepareButton = new JButton("Prepare");
prepareButton.setToolTipText("Prepare Simulation");
prepareButton.addActionListener(actionListener);
toolbar.add(prepareButton);
runButton = new JButton("Run");
runButton.setToolTipText("Run Simulation");
runButton.addActionListener(actionListener);
toolbar.add(runButton);
stepButton = new JButton("Step");
stepButton.setToolTipText("Execute Simulation Step");
stepButton.addActionListener(actionListener);
toolbar.add(stepButton);
pauseButton = new JToggleButton("Pause");
pauseButton.setToolTipText("Pause Simulation");
pauseButton.addActionListener(actionListener);
toolbar.add(pauseButton);
contentPanel.add(toolbar, java.awt.BorderLayout.NORTH);
messageLogger = new MessageLoggerPanel();
centerPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
centerPane.add(JSplitPane.RIGHT, messageLogger);
centerPane.setDividerSize(5);
centerPane.setResizeWeight(0.7);
contentPanel.add(centerPane, BorderLayout.CENTER);
JPanel statusPanel = new JPanel(new BorderLayout());
statusLabel = new JLabel("");
statusLabel.setBorder(new javax.swing.border.EtchedBorder()); // BevelBorder
statusPanel.add(statusLabel, BorderLayout.CENTER);
cancelButton = new JButton("Cancel");
cancelButton.setToolTipText("Cancel Simulation");
cancelButton.addActionListener(actionListener);
cancelButton.setPreferredSize(new java.awt.Dimension(80, 20));
cancelButton.setBorder(new javax.swing.border.EtchedBorder());
statusPanel.add(cancelButton, BorderLayout.EAST);
contentPanel.add(statusPanel, BorderLayout.SOUTH);
}
/** Enables/disables combos and buttons. */
public void updateEnabledState() {
boolean b = (getSimulationThread() == null);
boolean prep = b && controller != null && controller.isPrepared();
clearButton.setEnabled(b);
prepareButton.setEnabled(b);
runButton.setEnabled(prep);
if (b) pauseButton.setSelected(false);
pauseButton.setEnabled(!b);
stepButton.setEnabled(prep);
cancelButton.setEnabled(!b);
for (JComboBox combo : selectors.combos)
combo.setEnabled(b);
}
/** Tells the controller to prepare simulation and updates enabled state. */
protected void selectionChanged(String changedSelector) {
if (controller != null) {
controller.prepare(changedSelector);
updateEnabledState();
}
}
// ////////////////////////////////////////////////////////
// inner classes
/** Sends commands to the controller. */
private class FrameActionListener implements ActionListener {
@SuppressWarnings("deprecation")
public void actionPerformed(ActionEvent evt) {
String err = "";
try {
if (controller != null) {
setStatus("");
Object source = evt.getSource();
if (source == clearButton) {
// additionally clear the text area
err = "when clearing the views ";
messageLogger.clear();
statusLabel.setText("");
controller.clear();
} else if (source == prepareButton) {
err = "when preparing simulation ";
controller.prepare(null);
} else if (source == runButton) {
err = "when running simulation ";
setStatus("");
setSimulationThread(new SimulationThread
(AgentAppFrame.this, controller, false));
getSimulationThread().start();
} else if (source == stepButton) {
err = "when executing simulation step ";
setStatus("");
setSimulationThread(new SimulationThread
(AgentAppFrame.this, controller, true));
getSimulationThread().start();
} else if (source == cancelButton) {
err = "when canceling simulation ";
SimulationThread at = getSimulationThread();
if (at != null) {
if (!at.isCanceled()) {
at.interrupt();
} else {
// agent has ignored the interrupt
at.stop();
setStatus("Task stopped.");
setSimulationThread(null);
}
}
} else if (selectors.combos.contains(source)) {
err = "when preparing the agent ";
selectionChanged(selectors.getName((JComboBox) source));
}
}
} catch (Exception e) {
messageLogger.log("Error: Something went wrong " + err + "(" + e
+ ").");
e.printStackTrace();
}
updateEnabledState();
}
}
/** Maintains all selector comboboxes. */
private class SelectorContainer {
String[] selectorNames = new String[] {};
int[] selectorDefaults = new int[] {};
// JPanel selectorPanel = new JPanel();
List<JComboBox> combos = new ArrayList<JComboBox>();
public void setSelectors(String[] selectorNames, String[] tooltips) {
this.selectorNames = selectorNames;
this.selectorDefaults = new int[selectorNames.length];
for (JComboBox combo : combos)
toolbar.remove(combo);
combos.clear();
for (int i = 0; i < selectorNames.length; i++) {
JComboBox combo = new JComboBox();
combo.addActionListener(actionListener);
combo.setVisible(false);
combos.add(combo);
toolbar.add(combo, i);
if (tooltips != null)
combo.setToolTipText(tooltips[i]);
}
}
public void setSelectorItems(String selectorName, String[] items,
int defaultIdx) {
JComboBox combo = getCombo(selectorName);
combo.removeAllItems();
for (String item : items)
combo.addItem(item);
selectorDefaults[combos.indexOf(combo)] = defaultIdx;
combo.setVisible(items.length > 0);
}
public void setDefaults() {
for (int i = 0; i < selectorDefaults.length; i++) {
if (combos.get(i).getItemCount() > 0)
combos.get(i).setSelectedIndex(selectorDefaults[i]);
}
}
public SelectionState getSelection() {
SelectionState result = new SelectionState(selectorNames);
for (int i = 0; i < result.size(); i++) {
result.setValue(i, combos.get(i).getSelectedIndex());
}
return result;
}
JComboBox getCombo(String selectorName) {
for (int i = 0; i < selectorNames.length; i++)
if (selectorNames[i].equals(selectorName))
return combos.get(i);
return null;
}
String getName(JComboBox combo) {
int idx = combos.indexOf(combo);
if (idx != -1)
return selectorNames[idx];
else
return null;
}
}
// ////////////////////////////////////////////////////////
// static inner classes
/**
* Contains the names of all selectors and the indices of their selected
* items. Instances are used to communicate the selection state between the
* frame and the controller.
*/
public static class SelectionState {
private final List<String> selectors = new ArrayList<String>();
private final List<Integer> selIndices = new ArrayList<Integer>();
protected SelectionState(String[] selectors) {
for (String sel : selectors) {
this.selectors.add(sel);
this.selIndices.add(null);
}
}
/** Returns the number of selectors currently available. */
public int size() {
return selectors.size();
}
/**
* Sets the selection state of a specified selector to a specified item.
*/
void setValue(int selectorIdx, int valIdx) {
selIndices.set(selectorIdx, valIdx);
}
/**
* Sets the selection state of a specified selector to a specified item.
*/
void setValue(String selector, int valIdx) {
selIndices.set(selectors.indexOf(selector), valIdx);
}
/** Returns the index of the selected item of a specified selector. */
public int getValue(int selectorIdx) {
return selIndices.get(selectorIdx);
}
/** Returns the index of the selected item of a specified selector. */
public int getValue(String selector) {
return selIndices.get(selectors.indexOf(selector));
}
/** Returns a readable representation of the selection state. */
@Override
public String toString() {
StringBuffer result = new StringBuffer("State[ ");
for (int i = 0; i < size(); i++)
result.append(selectors.get(i) + "=" + selIndices.get(i) + " ");
result.append("]");
return result.toString();
}
}
}