/*
* JABM - Java Agent-Based Modeling Toolkit
* Copyright (C) 2013 Steve Phelps
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package net.sourceforge.jabm;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import net.infonode.docking.DockingWindow;
import net.infonode.docking.RootWindow;
import net.infonode.docking.SplitWindow;
import net.infonode.docking.TabWindow;
import net.infonode.docking.View;
import net.infonode.docking.properties.RootWindowProperties;
import net.infonode.docking.theme.DockingWindowsTheme;
import net.infonode.docking.theme.ShapedGradientDockingTheme;
import net.infonode.docking.util.DockingUtil;
import net.infonode.docking.util.ViewMap;
import net.sourceforge.jabm.report.Report;
import net.sourceforge.jabm.report.ReportWithGUI;
import net.sourceforge.jabm.util.SystemProperties;
import net.sourceforge.jabm.view.PropertiesEditor;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
/**
* The main class to run if running JABM with a graphical user interface.
* The simulation model is specified as a Spring beans xml configuration file,
* as per {@link SimulationManager}.
*
* @see SimulationManager
*
* @author Steve Phelps
*/
public class DesktopSimulationManager extends SimulationManager {
protected RootWindow desktopPane;
protected ViewMap viewMap;
protected JFrame desktopFrame;
protected LinkedList<View> reportViews;
protected LinkedList<View> builtinViews;
protected JMenuItem[] viewMenuItems;
protected DockingWindowsTheme currentTheme =
new ShapedGradientDockingTheme();
protected RootWindowProperties properties = new RootWindowProperties();
protected SimulationController simulationController;
protected Thread simulationThread;
protected Properties simulationProperties = null;
protected int propertiesViewId;
protected JButton runButton;
protected JToggleButton pauseButton;
protected JButton terminateButton;
protected View propertiesView;
protected PropertiesEditor propertiesEditor;
protected View outputView;
static Logger logger = Logger.getLogger(DesktopSimulationManager.class);
private static final int ICON_SIZE = 8;
private static final Icon VIEW_ICON = new Icon() {
public int getIconHeight() {
return ICON_SIZE;
}
public int getIconWidth() {
return ICON_SIZE;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
Color oldColor = g.getColor();
g.setColor(new Color(70, 70, 70));
g.fillRect(x, y, ICON_SIZE, ICON_SIZE);
g.setColor(new Color(100, 230, 100));
g.fillRect(x + 1, y + 1, ICON_SIZE - 2, ICON_SIZE - 2);
g.setColor(oldColor);
}
};
public void initialise() {
this.simulationController = getSimulationController();
loadSimulationProperties();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
initialiseGUI();
}
});
}
public void saveSimulationProperties() {
if (this.propFile != null) {
FileOutputStream out;
try {
out = new FileOutputStream(propFile);
String comments = getClass().toString();
simulationProperties.store(out, comments);
} catch (IOException e) {
handleExceptionWithErrorDialog(e);
}
}
}
public void loadSimulationProperties() {
if (this.simulationProperties == null) {
this.simulationProperties = new Properties();
}
if (this.propFile != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
simulationProperties.clear();
simulationProperties
.load(new FileInputStream(propFile));
if (propertiesEditor != null) {
propertiesEditor.propertiesChanged();
}
} catch (IOException e) {
handleExceptionWithErrorDialog(e);
}
}
});
}
}
public void initialiseViews() {
viewMap = new ViewMap();
reportViews = new LinkedList<View>();
int viewNumber = 0;
for (Report report : getReports()) {
if (report instanceof ReportWithGUI) {
String name = report.getName();
if (name == null || "".equals(name)) {
name = "Report " + viewNumber;
}
View view = new View(name, VIEW_ICON,
((ReportWithGUI) report).getComponent());
viewMap.addView(viewNumber, view);
reportViews.add(view);
viewNumber++;
}
}
builtinViews = new LinkedList<View>();
outputView =
new View("Output Console", VIEW_ICON, createOutputFrame());
builtinViews.add(outputView);
viewMap.addView(viewNumber, outputView);
viewNumber++;
if (this.simulationProperties != null) {
propertiesEditor = new PropertiesEditor(simulationProperties);
propertiesView = new View("Simulation Properties", VIEW_ICON,
new JScrollPane(propertiesEditor));
builtinViews.add(propertiesView);
viewMap.addView(viewNumber, propertiesView);
this.propertiesViewId = viewNumber;
viewNumber++;
}
}
public JComponent createOutputFrame() {
JTextArea outArea = new JTextArea(20, 50);
JScrollPane pane = new JScrollPane(outArea);
Logger.getRootLogger().addAppender(new JTextAreaAppender(outArea));
System.setErr(new PrintStream(new TextAreaOutputStream(outArea, System.err)));
return pane;
}
public void initialiseGUI() {
initialiseViews();
desktopPane = DockingUtil.createRootWindow(viewMap, true);
try {
restoreLayout();
} catch (IOException e) {
handleExceptionWithErrorDialog(e);
}
// Set gradient theme. The theme properties object is the super object of our properties object, which
// means our property value settings will override the theme values
properties.addSuperObject(currentTheme.getRootWindowProperties());
// Our properties object is the super object of the root window properties object, so all property values of the
// theme and in our property object will be used by the root window
desktopPane.getRootWindowProperties().addSuperObject(properties);
desktopFrame = new JFrame("JABM");
desktopFrame.getContentPane().add(desktopPane, BorderLayout.CENTER);
desktopFrame.getContentPane().add(createToolBar(), BorderLayout.NORTH);
desktopFrame.setPreferredSize(new Dimension(1200, 900));
desktopFrame.pack();
JMenuBar menuBar = new JMenuBar();
menuBar.add(createFileMenu());
menuBar.add(createViewMenu());
menuBar.add(createHelpMenu());
desktopFrame.setJMenuBar(menuBar);
desktopFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
desktopFrame.addWindowListener(new SaveLayoutOnExitWindowListener());
desktopFrame.setVisible(true);
}
protected JMenuItem createViewMenuItem(String title, final View view) {
JMenuItem result = new JMenuItem(title);
result.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (view.getRootWindow() != null) {
view.restoreFocus();
} else {
DockingUtil.addWindow(view, desktopPane);
}
}
});
return result;
}
public void populateViewMenu(JMenu menu, List<View> views) {
for (View view : views) {
menu.add(createViewMenuItem(view.getTitle(), view));
}
}
public JMenu createReportMenu() {
JMenu reportMenu = new JMenu("Reports");
populateViewMenu(reportMenu, reportViews);
return reportMenu;
}
protected JMenu createViewMenu() {
JMenu menu = new JMenu("View");
menu.setMnemonic(KeyEvent.VK_V);
menu.add(createReportMenu());
populateViewMenu(menu, builtinViews);
return menu;
}
protected JMenu createHelpMenu() {
JMenu menu = new JMenu("Help");
JMenuItem about = new JMenuItem("About");
about.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
helpDialog();
}
});
menu.add(about);
return menu;
}
protected void helpDialog() {
String modelDescription =
getSimulationController().getModelDescription();
String message = Version.getVerboseVersion() + "\n"
+ Version.getCopyright();
if (modelDescription != null) {
message += "\n\n" + "Model: " + modelDescription;
}
JOptionPane.showMessageDialog(this.desktopFrame,
message, "About JABM",
JOptionPane.INFORMATION_MESSAGE);
}
public JToolBar createToolBar() {
JToolBar toolBar = new JToolBar();
ImageIcon runIcon = createImageIcon("icons/Play24.gif",
"run");
runButton = new JButton(runIcon);
runButton.setToolTipText("Launch (a batch of) simulation(s)");
this.simulationThread = new Thread(this);
runButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
runButton.setEnabled(false);
simulationThread.start();
}
});
toolBar.add(runButton);
ImageIcon stopIcon = createImageIcon("icons/Stop24.gif",
"stop");
terminateButton = new JButton(stopIcon);
terminateButton.setToolTipText("Terminate all simulations");
terminateButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
terminate();
}
});
toolBar.add(terminateButton);
ImageIcon pauseIcon = createImageIcon("icons/Pause24.gif",
"pause");
pauseButton = new JToggleButton(pauseIcon);
pauseButton.setToolTipText("Pause the current simulation");
pauseButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (pauseButton.isSelected()) {
pause();
} else {
resume();
}
}
});
toolBar.add(pauseButton);
toolBar.addSeparator();
JSlider speedSlider = new JSlider(0, 1000, 0);
speedSlider.setToolTipText("Simulation step sleep interval in ms");
speedSlider.setMajorTickSpacing(250);
speedSlider.setPaintTicks(true);
speedSlider.setPaintLabels(true);
speedSlider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
simulationController.slow(((JSlider) e
.getSource()).getValue());
}
});
toolBar.add(speedSlider);
return toolBar;
}
/** Returns an ImageIcon, or null if the path was invalid. */
protected ImageIcon createImageIcon(String path,
String description) {
java.net.URL imgURL = getClass().getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL, description);
} else {
throw new RuntimeException("Couldn't find file: " + path);
}
}
protected JMenu createFileMenu() {
JMenu menu = new JMenu("File");
menu.setMnemonic(KeyEvent.VK_F);
JMenuItem open = new JMenuItem("Open");
open.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JFileChooser fc = new JFileChooser(propFile);
fc.setFileFilter(new FileNameExtensionFilter(
"JABM properties files", "properties"));
int status = fc.showOpenDialog(desktopFrame);
if (status == JFileChooser.APPROVE_OPTION) {
propFile = fc.getSelectedFile().getPath();
loadSimulationProperties();
propertiesView.restore();
propertiesView.restoreFocus();
}
}
});
open.setMnemonic(KeyEvent.VK_O);
menu.add(open);
JMenuItem save = new JMenuItem("Save");
save.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
saveSimulationProperties();
}
});
save.setMnemonic(KeyEvent.VK_S);
menu.add(save);
JMenuItem exit = new JMenuItem("Exit");
exit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
closeApplication();
}
});
exit.setMnemonic(KeyEvent.VK_X);
menu.add(exit);
return menu;
}
public void closeApplication() {
cleanUp();
System.exit(0);
}
public void cleanUp() {
try {
terminate();
saveLayout();
} catch (IOException ex) {
logger.error("Could not save layout: " + ex);
ex.printStackTrace();
}
}
@Override
public void run() {
outputView.restoreFocus();
this.pauseButton.setEnabled(true);
this.terminateButton.setEnabled(true);
runSingleExperiment();
this.runButton.setEnabled(true);
this.terminateButton.setEnabled(false);
this.pauseButton.setEnabled(false);
}
@Override
public void runSingleExperiment() {
try {
if (simulationProperties != null) {
super.runSingleExperiment(simulationProperties);
} else {
super.runSingleExperiment();
}
} catch (Exception e) {
handleExceptionWithErrorDialog(e);
}
// Ready the simulation thread for another run.
this.simulationThread = new Thread(this);
}
public String getLayoutFileName() throws IOException {
File propertiesFile = new File(SystemProperties.jabsConfiguration()
.getProperty(SystemProperties.PROPERTY_CONFIG));
int modelHash = propertiesFile.getCanonicalPath().hashCode();
String outFileName = System.getProperty("user.home") + "/"
+ ".jabm" + modelHash + ".layout";
return outFileName;
}
public void saveLayout() throws IOException {
logger.debug("Saving layout..");
FileOutputStream fos = new FileOutputStream(getLayoutFileName());
ObjectOutputStream out = new ObjectOutputStream(fos);
desktopPane.write(out);
out.close();
logger.debug("layout saved.");
}
public void restoreLayout() throws IOException {
File layoutFile = new File(getLayoutFileName());
if (layoutFile.exists()) {
FileInputStream fis = new FileInputStream(layoutFile);
ObjectInputStream in = new ObjectInputStream(fis);
desktopPane.read(in);
in.close();
} else {
intialiseLayout();
}
}
public void intialiseLayout() {
desktopPane.setWindow(new SplitWindow(false, 0.75f,
new TabWindow(getWindows(reportViews)),
new TabWindow(getWindows(builtinViews))));
}
public DockingWindow[] getWindows(List<View> windows) {
DockingWindow[] result = new DockingWindow[windows.size()];
int i = 0;
for(DockingWindow window : windows) {
result[i++] = window;
}
return result;
}
public void handleExceptionWithErrorDialog(Exception e) {
e.printStackTrace();
String message = e.getMessage();
if (message == null || "".equals(message)) {
message = "Fatal exception (see console output)";
} else if (message.length() > 80) {
message = message.substring(0, 80) + "...";
}
JOptionPane.showMessageDialog(this.desktopFrame,
message, "Error", JOptionPane.ERROR_MESSAGE);
}
public void terminate() {
getSimulationController().terminate();
}
public void pause() {
getSimulation().pause();
}
public void resume() {
getSimulation().resume();
}
public List<Report> getReports() {
return simulationController.getReports();
}
public Simulation getSimulation() {
return simulationController.getSimulation();
}
public static void main(String[] args) {
DesktopSimulationManager manager = new DesktopSimulationManager();
manager.initialise();
}
class JTextAreaAppender extends AppenderSkeleton {
JTextArea textControl;
public JTextAreaAppender(JTextArea t) {
super();
textControl = t;
}
@Override
public void close() {
}
@Override
public boolean requiresLayout() {
return false;
}
@Override
protected void append(LoggingEvent event) {
textControl.append(event.getRenderedMessage() + "\n");
textControl.setCaretPosition(textControl.getDocument().getLength());
}
}
class TextAreaOutputStream extends OutputStream {
protected JTextArea textControl;
protected OutputStream originalOut;
public TextAreaOutputStream(JTextArea control, OutputStream originalOut) {
textControl = control;
this.originalOut = originalOut;
}
public void write(int b) throws IOException {
textControl.append(String.valueOf((char) b));
textControl.setCaretPosition(textControl.getDocument().getLength());
originalOut.write(b);
}
}
class SaveLayoutOnExitWindowListener implements WindowListener {
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
cleanUp();
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
}
}