diff --git a/.gitignore b/.gitignore index 6c3982d0..03cacce2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.idea /.classpath +/.factorypath /.project /.settings/ /target/ diff --git a/README.md b/README.md index 12cc1a71..0b724e71 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ JAS-mine allows to separate data representation and management, which is automat It has built-in utilities for communicating with an underlying relational database. In addition, the platform provides standard tools which are frequently used both in agent-based modelling and dynamic microsimulations, like design of experiments (DOE), run-time monitoring and visualization with plots and graphs (GUI), I/O communication, statistical analysis. -This repository contains the core libraries, for the gui libraries see https://github.com/jasmineRepo/JAS-mine-gui. See https://github.com/jasmineRepo for a list of JAS-mine projects including demonstration models. +See https://github.com/jasmineRepo for a list of JAS-mine projects including demonstration models. See www.jas-mine.net for more details. diff --git a/pom.xml b/pom.xml index 0240fda6..6578daec 100644 --- a/pom.xml +++ b/pom.xml @@ -139,5 +139,42 @@ h2 2.4.240 + + + + org.apache.xmlgraphics + batik-dom + 1.19 + + + org.apache.xmlgraphics + batik-svggen + 1.19 + + + org.metawidget.modules + metawidget-all + 4.2 + + + org.jfree + jcommon + 1.0.24 + + + org.jfree + jfreechart + 1.5.6 + + + org.jdesktop + beansbinding + 1.2.1 + + + com.formdev + flatlaf + 3.7.1 + diff --git a/src/main/java/microsim/gui/GuiUtils.java b/src/main/java/microsim/gui/GuiUtils.java new file mode 100644 index 00000000..0d50aa7e --- /dev/null +++ b/src/main/java/microsim/gui/GuiUtils.java @@ -0,0 +1,177 @@ +package microsim.gui; + +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.Rectangle; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.beans.PropertyVetoException; + +import javax.swing.ImageIcon; +import javax.swing.JDesktopPane; +import javax.swing.JFrame; +import javax.swing.JInternalFrame; + +import microsim.engine.SimulationManager; +import microsim.gui.probe.ProbeFrame; +import microsim.gui.shell.MicrosimShell; +import microsim.gui.shell.SimulationWindow; + +public class GuiUtils { + + public static class WindowGrabber extends WindowAdapter { + private JInternalFrame frame; + + public WindowGrabber(JInternalFrame internalFrame) { + frame = internalFrame; + } + + public void windowActivated(WindowEvent e) { + e.getWindow().setVisible(false); + frame.setVisible(true); + frame.setSize(e.getWindow().getSize()); + } + + public void windowClosed(WindowEvent e) { + frame.setVisible(false); + } + } + + /** + * Opens a probe on the given object and return its reference. + * + * @param on + * Object to be probed. If on implements the IProbeFields + * interface the probe will use this set of fields. + * @param title + * The title of the probe frame. + * @param ownerModel + * The caller SimModel. + * @return A new instance of ProbeFrame. + */ + public static ProbeFrame openProbe(Object on, String title, + SimulationManager ownerModel) { + ProbeFrame pf = new ProbeFrame(on, title); + pf.setVisible(true); + + return pf; + } + + /** + * Opens a probe on the given object and return its reference. + * + * @param on + * Object to be probed. If on implements the IProbeFields + * interface the probe will use this set of fields. + * @param title + * The title of the probe frame. + * @return A new instance of ProbeFrame. + */ + public static ProbeFrame openProbe(Object on, String title) { + return openProbe(on, title, null); + } + + public static void addWindow(Frame window) { + if (MicrosimShell.currentShell == null) + window.setVisible(true); + else { + if (window instanceof JFrame) + addWindow(buildInternalFrame((JFrame) window)); + else { + SimulationWindow win = new SimulationWindow(null, + window.getTitle(), window); + win.setDefaultPosition(window.getBounds()); + + Rectangle r = win.getDefaultPosition(); + window.setBounds(r.x, r.y, r.width, r.height); + window.setVisible(true); + } + } + } + + public static void addWindow(Frame window, int x, int y, int width, int height) { + + } + + public static void addWindow(JInternalFrame window) { + + final JDesktopPane desk = MicrosimShell.currentShell.getJDesktopPane(); + + desk.add(window); + window.show(); + + JInternalFrame[] allframes = desk.getAllFrames(); + int count = allframes.length; + if (count == 0) + return; + + // Determine the necessary grid size + int sqrt = (int) Math.sqrt(count); + int rows = sqrt; + int cols = sqrt; + if (rows * cols < count) { + cols++; + if (rows * cols < count) { + rows++; + } + } + + // Define some initial values for size & location. + Dimension size = desk.getSize(); + + int x = 0; + int y = 0; + + int maxH = 0; + + for (int i = 0; i < allframes.length; i++) { + JInternalFrame f = allframes[i]; + if (!f.isClosed() && f.isIcon()) { + try { + f.setIcon(false); + } catch (PropertyVetoException ignored) { + } + } + + desk.getDesktopManager().resizeFrame(f, x, y, f.getWidth(), f.getHeight()); + if (f.getHeight() > maxH) + maxH = f.getHeight(); + x += f.getWidth(); + if (x > size.width) { + x = 0; + y += maxH; + maxH = 0; + } + } + } + + public static void addWindow(JInternalFrame window, int x, int y, + int width, int height) { + // SimulationWindow win = new SimulationWindow(null, window.getTitle(), + // window); + // win.setDefaultPosition(window.getBounds()); + + MicrosimShell.currentShell.getJDesktopPane().add(window); + // Rectangle r = win.getDefaultPosition(); + window.reshape(x, y, width, height); + window.show(); + } + + public static JInternalFrame buildInternalFrame(JFrame frame) { + JInternalFrame intF = new JInternalFrame(frame.getTitle(), + frame.isResizable(), false, frame.isResizable(), true); + + if (frame.getJMenuBar() != null) + intF.setJMenuBar(frame.getJMenuBar()); + + WindowGrabber wg = new WindowGrabber(intF); + frame.addWindowListener(wg); + + intF.getContentPane().add(frame.getContentPane()); + if (frame.getIconImage() != null) + intF.setFrameIcon(new ImageIcon(frame.getIconImage())); + intF.setSize(frame.getSize()); + return intF; + } + +} diff --git a/src/main/java/microsim/gui/colormap/ColorMap.java b/src/main/java/microsim/gui/colormap/ColorMap.java new file mode 100644 index 00000000..1268d308 --- /dev/null +++ b/src/main/java/microsim/gui/colormap/ColorMap.java @@ -0,0 +1,66 @@ +package microsim.gui.colormap; + +/** + * A generic interface for color mappers. This interface + * is required by {@code LayerDrawer} objects to + * paint values on the screen. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public interface ColorMap { + + /** + * Return the components of the color stored at given index. + * + * @param index The index of the color. It is a 0-based index of the color + * corresponding to the adding order. + * @return An array of 3 integers representing the RGB components of the color. + */ + public int[] getColorComponents(int index); + + /** + * Return the index of the color mapped to the given value. + * + * @param value The value mapped to the color. + * @return The array index of the requested color. + */ + public int getColorIndex(int value); + + /** + * Return the index of the color mapped to the given value. + * + * @param value The value mapped to the color. + * @return The array index of the requested color. + */ + public int getColorIndex(double value); + +} diff --git a/src/main/java/microsim/gui/colormap/DoubleRangeColorMap.java b/src/main/java/microsim/gui/colormap/DoubleRangeColorMap.java new file mode 100644 index 00000000..27b05dd6 --- /dev/null +++ b/src/main/java/microsim/gui/colormap/DoubleRangeColorMap.java @@ -0,0 +1,62 @@ +package microsim.gui.colormap; + +import java.awt.Color; + +/** + * It builds automatically a color map varying between two colors on a variable + * range. + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright: Copyright (c) 2002 under GPL library + *

+ * + * @author Matteo Morini and Michele Sonnessa + */ + +public class DoubleRangeColorMap extends FixedColorMap { + private int redStart, blueStart, greenStart; + private int redEnd, blueEnd, greenEnd; + private double rangeSize; + + public DoubleRangeColorMap(int gradients, Color bottomColor, Color topColor, + double minValue, double maxValue) { + super(gradients); + + if (maxValue <= minValue) + throw new ArrayIndexOutOfBoundsException("ColorDualRangeMap: range parameters are not corrected."); + + redStart = bottomColor.getRed(); + blueStart = bottomColor.getBlue(); + greenStart = bottomColor.getGreen(); + + redEnd = topColor.getRed(); + blueEnd = topColor.getBlue(); + greenEnd = topColor.getGreen(); + + rangeSize = (maxValue - minValue) / gradients; + + for (int i = 0; i < gradients; i++) { + // int[] c = getComponents(getBoundedCol(i * gap)); + int[] c = new int[] { 0, 0, 0 }; + c[0] = redStart + (int) ((redEnd - redStart) * i / gradients); + c[1] = greenStart + (int) ((greenEnd - redStart) * i / gradients); + c[2] = blueStart + (int) ((blueEnd - redStart) * i / gradients); + + addColor(i, new Color(c[0], c[1], c[2])); + } + } + + public int getColorIndex(double value) { + int i = (int) (value * rangeSize); + if (i < 0) + i = 0; + if (i >= colorList.length) + i = colorList.length - 1; + return i; + } +} diff --git a/src/main/java/microsim/gui/colormap/FixedColorMap.java b/src/main/java/microsim/gui/colormap/FixedColorMap.java new file mode 100644 index 00000000..de52cf47 --- /dev/null +++ b/src/main/java/microsim/gui/colormap/FixedColorMap.java @@ -0,0 +1,192 @@ +package microsim.gui.colormap; + +import java.awt.Color; +import java.util.HashMap; +import java.util.Map; + +/** + * An object used to map integer values to colors. + * It is used by {@code LayeredDrawer} to draw objects of the + * LayeredSurfaceFrame. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class FixedColorMap implements ColorMap { + protected Color[] colorList; + protected int[][] colorComponents; + protected int colors = 0; + protected Map mapper; + + /** Create a color map. */ + public FixedColorMap() { + colorList = new Color[0]; + colorComponents = new int[0][3]; + mapper = new HashMap(); + } + + /** + * Create a color map with an initial capacity of mapping positions. + * + * @param colors The number of colors to be mapped. Must be a non-zero positive. + */ + public FixedColorMap(int colors) { + if (colors <= 0) + throw new ArrayIndexOutOfBoundsException("ColorMap: 'colors' must be a positive number."); + + colorList = new Color[colors]; + colorComponents = new int[colors][3]; + mapper = new HashMap(colors); + } + + private void ensureCapacity(int unitsRequired) { + if (colorList.length > unitsRequired) + return; + + Color[] c = new Color[unitsRequired]; + int[][] cc = new int[unitsRequired][3]; + + System.arraycopy(colorList, 0, c, 0, colorList.length); + System.arraycopy(colorComponents, 0, cc, 0, colorComponents.length); + + colorList = c; + colorComponents = cc; + } + + /** + * Add a color to the map. + * + * @param value The integer value that maps to the given color. + * @param color The color to be mapped. + * @throws ArrayIndexOutOfBoundsException If value is greather than the defined + * mapped colors. + */ + public void addColor(int value, Color color) { + if (value < 0) + throw new IllegalArgumentException("ColorMap.addColor: value parameter must be positive."); + + if (mapper.containsKey(value)) + throw new IllegalArgumentException("ColorMap.addColor: value " + value + " already added."); + + ensureCapacity(colors + 1); + + colorList[colors] = color; + colorComponents[colors][0] = color.getRed(); + colorComponents[colors][1] = color.getGreen(); + colorComponents[colors][2] = color.getBlue(); + mapper.put(value, colors++); + } + + /** + * Add a color to the map. + * + * @param value The integer value that maps to the given color. + * @param red The red component of the color to be mapped. [0-255] range + * accepted. + * @param green The green component of the color to be mapped. [0-255] range + * accepted. + * @param blue The blue component of the color to be mapped. [0-255] range + * accepted. + * @throws ArrayIndexOutOfBoundsException If one of the three color components + * is out of (0, 255) range. + */ + public void addColor(int value, int red, int green, int blue) { + if (red < 0 || red > 255) + throw new ArrayIndexOutOfBoundsException("ColorMap.addColor: Red component must be in range [0, 255]"); + if (green < 0 || green > 255) + throw new ArrayIndexOutOfBoundsException("ColorMap.addColor: Green component must be in range [0, 255]"); + if (blue < 0 || blue > 255) + throw new ArrayIndexOutOfBoundsException("ColorMap.addColor: Blue component must be in range [0, 255]"); + + addColor(value, new Color(red, green, blue)); + } + + /** + * Return the color to at the given index position. + * + * @param index The value to be mapped. + * @return The color corresponding to the value. + */ + public Color getColor(int index) { + return colorList[index]; + } + + /** + * Return the color list. + * + * @return An array of Color. The index represent the mapping value. + */ + public Color[] toArray() { + return colorList; + } + + public int[] getColorComponents(int index) { + return colorComponents[index]; + } + + /** + * Return the color index. + * + * @param value The value to be mapped. + * @return The index of the color list mapping the value. + */ + public int getColorIndex(int value) { + int k = mapper.get(value); + if (k == Integer.MIN_VALUE) + throw new ArrayIndexOutOfBoundsException("ColorMap.getColorIndex: Value " + + value + " not mapped."); + return k; + } + + /** + * Return the color index. + * + * @param value The value to be mapped. + * @return The index of the color list mapping the value. + */ + public int getColorIndex(double value) { + int k = mapper.get((int) value); + if (k == Integer.MIN_VALUE) + throw new ArrayIndexOutOfBoundsException("ColorMap.getColorIndex: Value " + + value + " not mapped."); + return k; + } + + /** + * Map the given value with the right color. + * + * @param value The value to be mapped. + * @return The color corresponding to the value. + */ + public Color getMappedColor(int value) { + return colorList[getColorIndex(value)]; + } +} diff --git a/src/main/java/microsim/gui/colormap/RangeColorMap.java b/src/main/java/microsim/gui/colormap/RangeColorMap.java new file mode 100644 index 00000000..548cce94 --- /dev/null +++ b/src/main/java/microsim/gui/colormap/RangeColorMap.java @@ -0,0 +1,231 @@ +package microsim.gui.colormap; + +import java.awt.Color; + +/** + * It builds automatically a color map using a variable range.
+ * There are three types of range:
+ *

+ * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + * @author Matteo Morini + */ +public class RangeColorMap extends FixedColorMap { + protected double rangeSize; + protected double minValue; + + /** + * Create a color range map from black to given color. + * + * @param gradients The number of color gradients that are added to the map. + * @param minValue The lower bound of the range. + * @param maxValue The upper bound of the range. + * @param color The highest color. It will correspond to maxValue. + * @throws ArrayIndexOutOfBoundsException if {@code maxValue <= minValue}. + */ + public RangeColorMap(int gradients, double minValue, double maxValue, + Color color) { + super(gradients); + + if (maxValue <= minValue) + throw new ArrayIndexOutOfBoundsException("ColorRangeMap: range parameters" + + " are not corrected."); + + this.rangeSize = gradients / (maxValue - minValue); + this.minValue = minValue; + + double redGap = (double) color.getRed() / (double) gradients; + double greenGap = (double) color.getGreen() / (double) gradients; + double blueGap = (double) color.getBlue() / (double) gradients; + + for (int i = 0; i < gradients; i++) + addColor(i, new Color(getBoundedCol(redGap * i), + getBoundedCol(greenGap * i), + getBoundedCol(blueGap * i))); + + } + + /** + * Create a color range map from given color to given color. + * + * @param gradients The number of color gradients that are added to the map. + * @param minValue The lower bound of the range. + * @param maxValue The upper bound of the range. + * @param bottomColor The lowest color. It will correspond to minValue. + * @param topColor The highest color. It will correspond to maxValue. + * @throws ArrayIndexOutOfBoundsException if {@code maxValue <= minValue}. + */ + public RangeColorMap(int gradients, double minValue, double maxValue, + Color bottomColor, Color topColor) { + super(gradients); + + if (maxValue <= minValue) + throw new ArrayIndexOutOfBoundsException("ColorRangeMap: range parameters are not corrected."); + + this.minValue = minValue; + int redStart, blueStart, greenStart; + int redGap, blueGap, greenGap; + + redStart = bottomColor.getRed(); + blueStart = bottomColor.getBlue(); + greenStart = bottomColor.getGreen(); + + redGap = topColor.getRed() - redStart; + blueGap = topColor.getBlue() - blueStart; + greenGap = topColor.getGreen() - greenStart; + + // rangeSize = (maxValue - minValue) / (double) gradients; + rangeSize = (double) gradients / (maxValue - minValue); + + int[] c = new int[] { 0, 0, 0 }; + for (int i = 0; i < gradients; i++) { + // int[] c = getComponents(getBoundedCol(i * gap)); + double delta = (double) i / (double) gradients; + c[0] = getBoundedCol(redStart + (int) (redGap * delta)); + c[1] = getBoundedCol(greenStart + (int) (greenGap * delta)); + c[2] = getBoundedCol(blueStart + (int) (blueGap * delta)); + + addColor(i, new Color(c[0], c[1], c[2])); + } + } + + /** + * Create a color range map from lower given color to middle given color and + * from middle to the highest given one. + * + * @param gradients The number of color gradients that are added to the map. + * @param minValue The lower bound of the range. + * @param midValue The value at which color changes the range. + * @param maxValue The upper bound of the range. + * @param bottomColor The lowest color. It will correspond to minValue. + * @param middleColor The middle color. It will correspond to midValue. + * @param topColor The highest color. It will correspond to maxValue. + * @throws ArrayIndexOutOfBoundsException if + * {@code maxValue <= midValue || midValue <= minValue}. + */ + public RangeColorMap(int gradients, double minValue, double midValue, + double maxValue, Color bottomColor, Color middleColor, + Color topColor) { + super(gradients); + + if (maxValue <= midValue || midValue <= minValue) + throw new ArrayIndexOutOfBoundsException("ColorRangeMap: range parameters are not corrected."); + + int redStart, blueStart, greenStart; + int redGap, blueGap, greenGap; + int[] c = new int[] { 0, 0, 0 }; + + this.minValue = minValue; + + rangeSize = (double) gradients / (maxValue - minValue); + // LOW PART + int lowGradients = (int) (gradients * ((midValue - minValue) / (maxValue - minValue))); + + redStart = bottomColor.getRed(); + blueStart = bottomColor.getBlue(); + greenStart = bottomColor.getGreen(); + + redGap = middleColor.getRed() - redStart; + blueGap = middleColor.getBlue() - blueStart; + greenGap = middleColor.getGreen() - greenStart; + + for (int i = 0; i < lowGradients; i++) { + double delta = (double) i / (double) lowGradients; + c[0] = getBoundedCol(redStart + (int) (redGap * delta)); + c[1] = getBoundedCol(greenStart + (int) (greenGap * delta)); + c[2] = getBoundedCol(blueStart + (int) (blueGap * delta)); + + addColor(i, new Color(c[0], c[1], c[2])); + } + + // HIGH PART + int highGradients = gradients - lowGradients; + + redStart = middleColor.getRed(); + blueStart = middleColor.getBlue(); + greenStart = middleColor.getGreen(); + + redGap = topColor.getRed() - redStart; + blueGap = topColor.getBlue() - blueStart; + greenGap = topColor.getGreen() - greenStart; + + for (int i = lowGradients; i < gradients; i++) { + double delta = (double) (i - lowGradients) / (double) highGradients; + c[0] = getBoundedCol(redStart + (int) (redGap * delta)); + c[1] = getBoundedCol(greenStart + (int) (greenGap * delta)); + c[2] = getBoundedCol(blueStart + (int) (blueGap * delta)); + + addColor(i, new Color(c[0], c[1], c[2])); + } + } + + private int getBoundedCol(double d) { + return Math.min(255, (int) d); + } + + /** + * Return the color index. + * + * @param value The value to be mapped. If it is outside the range bounds, + * the method returns its nearest bound. + * @return The index of the color list mapping the value. + */ + public int getColorIndex(int value) { + int i = (int) ((value - minValue) * rangeSize); + if (i < 0) + i = 0; + if (i >= colorList.length) + i = colorList.length - 1; + return i; + } + + /** + * Return the color index. + * + * @param value The value to be mapped. If it is outside the range bounds, + * the method returns its nearest bound. + * @return The index of the color list mapping the value. + */ + public int getColorIndex(double value) { + int i = (int) ((value - minValue) * rangeSize); + if (i < 0) + i = 0; + if (i >= colorList.length) + i = colorList.length - 1; + return i; + } + +} diff --git a/src/main/java/microsim/gui/colormap/TripleRangeColorMap.java b/src/main/java/microsim/gui/colormap/TripleRangeColorMap.java new file mode 100644 index 00000000..99ca3b61 --- /dev/null +++ b/src/main/java/microsim/gui/colormap/TripleRangeColorMap.java @@ -0,0 +1,101 @@ +package microsim.gui.colormap; + +import java.awt.Color; + +/** + * It builds automatically a color map oscillating from a bottom color to a + * middle one and from the middle + * to a top one, on a variable range.
+ * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + * + */ +public class TripleRangeColorMap extends FixedColorMap { + + private int redStart, blueStart, greenStart; + private int redMiddle, blueMiddle, greenMiddle; + private int redEnd, blueEnd, greenEnd; + private double rangeSize; + + public TripleRangeColorMap(int gradients, Color bottomColor, Color middleColor, Color topColor, + double minValue, double midValue, double maxValue) { + super(gradients); + + if (maxValue <= midValue || midValue <= minValue) + throw new ArrayIndexOutOfBoundsException("ColorTripleRangeMap: range parameters are not corrected."); + + redStart = bottomColor.getRed(); + blueStart = bottomColor.getBlue(); + greenStart = bottomColor.getGreen(); + + redMiddle = middleColor.getRed(); + blueMiddle = middleColor.getBlue(); + greenMiddle = middleColor.getGreen(); + + redEnd = topColor.getRed(); + blueEnd = topColor.getBlue(); + greenEnd = topColor.getGreen(); + + rangeSize = (maxValue - minValue) / gradients; + + int lowGradients = (int) ((midValue - minValue) / (maxValue - minValue) * gradients); + + for (int i = 0; i < lowGradients; i++) { + // int[] c = getComponents(getBoundedCol(i * gap)); + int[] c = new int[] { 0, 0, 0 }; + c[0] = redStart + (int) ((redMiddle - redStart) * i / gradients); + c[1] = greenStart + (int) ((greenMiddle - greenStart) * i / gradients); + c[2] = blueStart + (int) ((blueMiddle - blueStart) * i / gradients); + + addColor(i, new Color(c[0], c[1], c[2])); + } + + for (int i = lowGradients; i < gradients; i++) { + // int[] c = getComponents(getBoundedCol(i * gap)); + int[] c = new int[] { 0, 0, 0 }; + c[0] = redMiddle + (int) ((redEnd - redMiddle) * i / gradients); + c[1] = greenMiddle + (int) ((greenEnd - greenMiddle) * i / gradients); + c[2] = blueMiddle + (int) ((blueEnd - blueMiddle) * i / gradients); + + addColor(i, new Color(c[0], c[1], c[2])); + } + + } + + public int getColorIndex(double value) { + int i = (int) (value * rangeSize); + if (i < 0) + i = 0; + if (i >= colorList.length) + i = colorList.length - 1; + return i; + } +} diff --git a/src/main/java/microsim/gui/plot/CollectionBarSimulationPlotter.java b/src/main/java/microsim/gui/plot/CollectionBarSimulationPlotter.java new file mode 100644 index 00000000..4a8357db --- /dev/null +++ b/src/main/java/microsim/gui/plot/CollectionBarSimulationPlotter.java @@ -0,0 +1,316 @@ +package microsim.gui.plot; + +import java.awt.Color; +import java.util.ArrayList; + +import javax.swing.JInternalFrame; + +import microsim.event.CommonEventType; +import microsim.event.EventListener; +import microsim.statistics.IDoubleArraySource; +import microsim.statistics.IFloatArraySource; +import microsim.statistics.IIntArraySource; +import microsim.statistics.ILongArraySource; +import microsim.statistics.IUpdatableSource; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.CategoryAxis; +import org.jfree.chart.axis.CategoryLabelPositions; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.CategoryPlot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.renderer.category.BarRenderer; +import org.jfree.data.category.DefaultCategoryDataset; + +/** + * A bar chart plotter showing elements manually added by user. It is based on + * JFreeChart library. It is compatible with the microsim.statistics.* classes. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002-13 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class CollectionBarSimulationPlotter extends JInternalFrame implements EventListener { + + private static final long serialVersionUID = 1L; + + private ArrayList sources; + private ArrayList categories; + + private DefaultCategoryDataset dataset; + + private BarRenderer renderer; + + private Integer maxBars; + + private abstract class ArraySource { + // public String label; + protected boolean isUpdatable; + + public abstract double[] getDoubleArray(); + + } + + private class DArraySource extends ArraySource { + public IDoubleArraySource source; + + public DArraySource(String label, IDoubleArraySource source) { + // super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getDoubleArray(); + } + } + + private class FArraySource extends ArraySource { + public IFloatArraySource source; + + public FArraySource(String label, IFloatArraySource source) { + // super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + float[] array = source.getFloatArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + } + + private class IArraySource extends ArraySource { + public IIntArraySource source; + + public IArraySource(String label, IIntArraySource source) { + // super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + int[] array = source.getIntArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + } + + private class LArraySource extends ArraySource { + public ILongArraySource source; + + public LArraySource(String label, ILongArraySource source) { + // super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + long[] array = source.getLongArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + } + + public CollectionBarSimulationPlotter(String title, String yaxis) { + super(); + this.setResizable(true); + this.setTitle(title); + + sources = new ArrayList(); + categories = new ArrayList(); + + dataset = new DefaultCategoryDataset(); + + final JFreeChart chart = ChartFactory.createBarChart( + title, // chart title + "Categories", // x axis label + yaxis, // y axis label + dataset, // data + PlotOrientation.VERTICAL, + false, // include legend + true, // tooltips + false // urls + ); + + // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... + chart.setBackgroundPaint(Color.white); + + // get a reference to the plot for further customisation... + final CategoryPlot plot = chart.getCategoryPlot(); + plot.setBackgroundPaint(Color.lightGray); + plot.setDomainGridlinePaint(Color.white); + plot.setRangeGridlinePaint(Color.white); + + // set the range axis to display integers only... Ross: Why??? + final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); + // rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); + + // disable bar outlines... + renderer = (BarRenderer) plot.getRenderer(); + renderer.setDrawBarOutline(false); + + final CategoryAxis domainAxis = plot.getDomainAxis(); + domainAxis.setCategoryLabelPositions( + CategoryLabelPositions.createUpRotationLabelPositions(Math.PI / 6.0)); + // OPTIONAL CUSTOMISATION COMPLETED. + + final ChartPanel chartPanel = new ChartPanel(chart); + + chartPanel.setPreferredSize(new java.awt.Dimension(500, 270)); + + setContentPane(chartPanel); + + this.setSize(400, 400); + } + + public void onEvent(Enum type) { + if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) { + update(); + } + } + + public void update() { + for (int i = 0; i < sources.size(); i++) { + ArraySource cs = (ArraySource) sources.get(i); + final String category = categories.get(i); + + double[] vals = cs.getDoubleArray(); + for (int j = 0; j < vals.length && (j < (maxBars == null ? Integer.MAX_VALUE : maxBars)); j++) + dataset.addValue(vals[j], category, "" + j); + } + } + + /** + * Add a new series buffer, retrieving value from IDoubleSource objects in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, IDoubleArraySource source) { + DArraySource sequence = new DArraySource(name, source); + sources.add(sequence); + categories.add(name); + } + + /** + * Add a new series buffer, retrieving value from IDoubleSource objects in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, IFloatArraySource source) { + FArraySource sequence = new FArraySource(name, source); + sources.add(sequence); + categories.add(name); + } + + /** + * Add a new series buffer, retrieving value from IDoubleSource objects in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, IIntArraySource source) { + IArraySource sequence = new IArraySource(name, source); + sources.add(sequence); + categories.add(name); + } + + /** + * Add a new series buffer, retrieving value from IDoubleSource objects in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, ILongArraySource source) { + LArraySource sequence = new LArraySource(name, source); + sources.add(sequence); + categories.add(name); + } + + public Integer getMaxBars() { + return maxBars; + } + + public void setMaxBars(Integer maxBars) { + this.maxBars = maxBars; + } + +} diff --git a/src/main/java/microsim/gui/plot/HistogramSimulationPlotter.java b/src/main/java/microsim/gui/plot/HistogramSimulationPlotter.java new file mode 100644 index 00000000..51d1daa5 --- /dev/null +++ b/src/main/java/microsim/gui/plot/HistogramSimulationPlotter.java @@ -0,0 +1,406 @@ +package microsim.gui.plot; + +import java.awt.Color; +import java.util.ArrayList; + +import javax.swing.JInternalFrame; + +import microsim.engine.SimulationEngine; +import microsim.event.CommonEventType; +import microsim.event.EventListener; +import microsim.statistics.IDoubleArraySource; +import microsim.statistics.IFloatArraySource; +import microsim.statistics.IIntArraySource; +import microsim.statistics.ILongArraySource; +import microsim.statistics.IUpdatableSource; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.StandardXYBarPainter; +import org.jfree.chart.renderer.xy.XYBarRenderer; +import org.jfree.data.general.SeriesChangeEvent; +import org.jfree.data.statistics.HistogramDataset; +import org.jfree.data.statistics.HistogramType; + +/** + * A HistogramSimulationPlotter is able to display a histogram of one or more + * data + * sources, which can be updated during the simulation. It is based on + * JFreeChart + * library and uses data sources based on the microsim.statistics.* + * interfaces.
+ * + * + *

+ * Title: JAS-mine + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2017 Ross Richardson + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Ross Richardson + *

+ */ +public class HistogramSimulationPlotter extends JInternalFrame implements EventListener { + + private static final long serialVersionUID = 1L; + + final JFreeChart chart; + + private ArrayList sources; + + private HistogramDataset dataset; + + private HistogramType type; + + private int bins; + + private Double minimum; + + private Double maximum; + + /** + * Constructor for histogram chart objects with chart legend displayed by + * default and + * all data samples shown, showing only the latest population data as time moves + * forward. + * Note - values falling on the boundary of adjacent bins will be assigned to + * the higher + * indexed bin. If it is desired set the minimum and maximum values displayed, + * or to turn + * the legend off, use the constructor: + * HistogramSimulationPlotter(String title, String xaxis, HistogramType type, + * int bins, double minimum, double maximum, boolean includeLegend) + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param type - the type of the histogram: either FREQUENCY, + * RELATIVE_FREQUENCY, or SCALE_AREA_TO_1 + * @param bins - the number of bins in the histogram + * + */ + public HistogramSimulationPlotter(String title, String xaxis, HistogramType type, int bins) { // Includes legend by + // default and will + // accumulate data + // samples by default + // (if wanting only + // the most recent + // data points, use + // the other + // constructor) + this(title, xaxis, type, bins, null, null, true); + } + + /** + * Constructor for scatterplot chart objects, featuring a toggle to hide the + * chart legend + * and to set the minimum and maximum values displayed in the chart, with values + * below the minimum + * assigned to the first bin, and values above the maximum assigned to the last + * bin. Note - + * values falling on the boundary of adjacent bins will be assigned to the + * higher indexed bin. + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param type - the type of the histogram: either FREQUENCY, + * RELATIVE_FREQUENCY, or SCALE_AREA_TO_1 + * @param bins - the number of bins in the histogram + * @param minimum - any data value less than minimum will be assigned to + * the first bin + * @param maximum - any data value greater than maximum will be assigned + * to the last bin + * @param includeLegend - toggles whether to include the legend. If displaying a + * very large number of different series in the chart, it + * may be useful to turn + * the legend off as it will occupy a lot of space in the + * GUI. + */ + public HistogramSimulationPlotter(String title, String xaxis, HistogramType type, int bins, Double minimum, + Double maximum, boolean includeLegend) { // Can specify whether to include legend and how many samples + // (updates) to display + super(); + this.setResizable(true); + this.setTitle(title); + this.type = type; + this.bins = bins; + this.minimum = minimum; + this.maximum = maximum; + + sources = new ArrayList(); + + dataset = new HistogramDataset(); + + String yaxis; + if (type.equals(HistogramType.FREQUENCY)) { + yaxis = "Frequency"; + } else if (type.equals(HistogramType.RELATIVE_FREQUENCY)) { + yaxis = "Relative Frequency"; + } else if (type.equals(HistogramType.SCALE_AREA_TO_1)) { + yaxis = "Density (area scaled to 1)"; + } else + throw new IllegalArgumentException( + "Incorrect HistogramType argument when calling HistogramSimulationPlotter constructor!"); + + chart = ChartFactory.createHistogram( + title, // chart title + xaxis, // x axis label + yaxis, // y axis label + // type.toString(), //y axis label based on the type of the histogram + dataset, // data + PlotOrientation.VERTICAL, + includeLegend, // include legend + true, // tooltips + false // urls + ); + + // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... + chart.setBackgroundPaint(Color.white); + + // get a reference to the plot for further customisation... + final XYPlot plot = chart.getXYPlot(); + // plot.setBackgroundPaint(Color.lightGray); + plot.setBackgroundPaint(Color.white); + plot.setDomainGridlinePaint(Color.white); + plot.setRangeGridlinePaint(Color.white); + plot.setForegroundAlpha(0.85f); + + final XYBarRenderer renderer = new XYBarRenderer(); + renderer.setDrawBarOutline(false); + renderer.setBarPainter(new StandardXYBarPainter()); + renderer.setShadowVisible(false); + plot.setRenderer(renderer); + + final NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis(); + domainAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); + final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); + rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); + + final ChartPanel chartPanel = new ChartPanel(chart); + + chartPanel.setPreferredSize(new java.awt.Dimension(500, 270)); + + setContentPane(chartPanel); + + this.setSize(400, 400); + } + + public void onEvent(Enum type) { + if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) { + update(); + } + } + + public void update() { + + dataset = new HistogramDataset(); + dataset.setType(type); + chart.getXYPlot().setDataset(dataset); + + // int s = 0; + // Color color = (Color) chart.getXYPlot().getRenderer().getItemPaint(s, 0); + // int r = color.getRed(); + // int g = color.getGreen(); + // int b = color.getBlue(); + // chart.getXYPlot().getRenderer().setSeriesPaint(s, new Color(r, g, b, 130)); + + for (int i = 0; i < sources.size(); i++) { + ArraySource cs = (ArraySource) sources.get(i); + double[] vals = cs.getDoubleArray(); + + if (minimum != null && maximum != null) { + dataset.addSeries(cs.label, vals, bins, minimum, maximum); + } else + dataset.addSeries(cs.label, vals, bins); + + } + dataset.seriesChanged( + new SeriesChangeEvent(new String("Update at time " + SimulationEngine.getInstance().getTime()))); + + } + + private abstract class ArraySource { + public String label; + protected boolean isUpdatable; + + public abstract double[] getDoubleArray(); + + } + + private class DArraySource extends ArraySource { + public IDoubleArraySource source; + + public DArraySource(String label, IDoubleArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getDoubleArray(); + } + } + + private class FArraySource extends ArraySource { + public IFloatArraySource source; + + public FArraySource(String label, IFloatArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + float[] array = source.getFloatArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + } + + private class IArraySource extends ArraySource { + public IIntArraySource source; + + public IArraySource(String label, IIntArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + int[] array = source.getIntArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + } + + private class LArraySource extends ArraySource { + public ILongArraySource source; + + public LArraySource(String label, ILongArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + long[] array = source.getLongArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + } + + /** + * Add a new series buffer, retrieving value from IDoubleSource objects in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, IDoubleArraySource source) { + DArraySource sequence = new DArraySource(name, source); + sources.add(sequence); + } + + /** + * Add a new series buffer, retrieving value from IDoubleSource objects in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, IFloatArraySource source) { + FArraySource sequence = new FArraySource(name, source); + sources.add(sequence); + } + + /** + * Add a new series buffer, retrieving value from IDoubleSource objects in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, IIntArraySource source) { + IArraySource sequence = new IArraySource(name, source); + sources.add(sequence); + } + + /** + * Add a new series buffer, retrieving value from IDoubleSource objects in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, ILongArraySource source) { + LArraySource sequence = new LArraySource(name, source); + sources.add(sequence); + } + +} diff --git a/src/main/java/microsim/gui/plot/IndividualBarSimulationPlotter.java b/src/main/java/microsim/gui/plot/IndividualBarSimulationPlotter.java new file mode 100644 index 00000000..8b36f33a --- /dev/null +++ b/src/main/java/microsim/gui/plot/IndividualBarSimulationPlotter.java @@ -0,0 +1,506 @@ +package microsim.gui.plot; + +import java.awt.Color; +import java.awt.Paint; +import java.util.ArrayList; + +import javax.swing.JInternalFrame; + +import microsim.event.CommonEventType; +import microsim.event.EventListener; +import microsim.gui.colormap.ColorMap; +import microsim.gui.colormap.FixedColorMap; +import microsim.reflection.ReflectionUtils; +import microsim.statistics.IDoubleSource; +import microsim.statistics.IFloatSource; +import microsim.statistics.IIntSource; +import microsim.statistics.ILongSource; +import microsim.statistics.IUpdatableSource; +import microsim.statistics.reflectors.DoubleInvoker; +import microsim.statistics.reflectors.FloatInvoker; +import microsim.statistics.reflectors.IntegerInvoker; +import microsim.statistics.reflectors.LongInvoker; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.CategoryAxis; +import org.jfree.chart.axis.CategoryLabelPositions; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.CategoryPlot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.renderer.category.BarRenderer; +import org.jfree.data.category.DefaultCategoryDataset; + +/** + * A bar chart plotter showing elements manually added by user. It is based on + * JFreeChart library. It is compatible with the microsim.statistics.* classes. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002-13 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + */ +public class IndividualBarSimulationPlotter extends JInternalFrame implements EventListener { + + private static final long serialVersionUID = 1L; + + private ArrayList sources; + private ArrayList categories; + + private DefaultCategoryDataset dataset; + + private BarRenderer renderer; + + private String yaxis; + + private FixedColorMap colorMap; + + public IndividualBarSimulationPlotter(String title, String yaxis) { + super(); + this.setResizable(true); + this.setTitle(title); + this.yaxis = yaxis; + colorMap = new FixedColorMap(); + + sources = new ArrayList(); + categories = new ArrayList(); + + dataset = new DefaultCategoryDataset(); + + final JFreeChart chart = ChartFactory.createBarChart( + title, // chart title + "Categories", // x axis label + yaxis, // y axis label + dataset, // data + PlotOrientation.VERTICAL, + false, // include legend + true, // tooltips + false // urls + ); + + // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... + chart.setBackgroundPaint(Color.white); + + // get a reference to the plot for further customisation... + final CategoryPlot plot = chart.getCategoryPlot(); + plot.setBackgroundPaint(Color.lightGray); + plot.setDomainGridlinePaint(Color.white); + plot.setRangeGridlinePaint(Color.white); + + // set the range axis to display integers only... Ross: Why??? + final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); + // rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); + + // disable bar outlines... + // renderer = (BarRenderer) plot.getRenderer(); + renderer = new ColoredBarRenderer(colorMap); + plot.setRenderer(renderer); + renderer.setDrawBarOutline(false); + + final CategoryAxis domainAxis = plot.getDomainAxis(); + domainAxis.setCategoryLabelPositions( + CategoryLabelPositions.createUpRotationLabelPositions(Math.PI / 6.0)); + // OPTIONAL CUSTOMISATION COMPLETED. + + final ChartPanel chartPanel = new ChartPanel(chart); + + chartPanel.setPreferredSize(new java.awt.Dimension(500, 270)); + + setContentPane(chartPanel); + + this.setSize(400, 400); + } + + public void onEvent(Enum type) { + if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) { + update(); + } + } + + public void update() { + for (int i = 0; i < sources.size(); i++) { + Source source = sources.get(i); + double d = source.getDouble(); + String category = categories.get(i); + + dataset.addValue(d, yaxis, category); + } + } + + private abstract class Source { + // public String label; + public Enum vId; + protected boolean isUpdatable; + + public abstract double getDouble(); + + } + + private class DSource extends Source { + public IDoubleSource source; + + public DSource(String label, IDoubleSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getDoubleValue(vId); + } + } + + private class FSource extends Source { + public IFloatSource source; + + public FSource(String label, IFloatSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getFloatValue(vId); + } + } + + private class ISource extends Source { + public IIntSource source; + + public ISource(String label, IIntSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getIntValue(vId); + } + } + + private class LSource extends Source { + public ILongSource source; + + public LSource(String label, ILongSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getLongValue(vId); + } + } + + /** + * Build a series retrieving data from a IDoubleSource object, using the + * default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IDoubleSource + * interface. + */ + public void addSources(String legend, IDoubleSource plottableObject) { + sources.add(new DSource(legend, plottableObject, IDoubleSource.Variables.Default)); + // set up gradient paints for series... + categories.add(legend); + } + + /** + * Build a series from a generic object. + * + * @param legend + * The legend name of the series. + * @param target + * The data source object. + * @param variableName + * The variable or method name of the source object. + * @param getFromMethod + * Specifies if the variableName is a field or a method. + */ + public void addSources(String legend, Object target, String variableName, + boolean getFromMethod) { + Source source = null; + if (ReflectionUtils.isDoubleSource(target.getClass(), variableName, + getFromMethod)) + source = new DSource(legend, new DoubleInvoker(target, + variableName, getFromMethod), IDoubleSource.Variables.Default); + else if (ReflectionUtils.isFloatSource(target.getClass(), variableName, + getFromMethod)) + source = new FSource(legend, new FloatInvoker(target, variableName, + getFromMethod), IFloatSource.Variables.Default); + else if (ReflectionUtils.isIntSource(target.getClass(), variableName, + getFromMethod)) + source = new ISource(legend, new IntegerInvoker(target, + variableName, getFromMethod), IIntSource.Variables.Default); + else if (ReflectionUtils.isLongSource(target.getClass(), variableName, + getFromMethod)) + source = new LSource(legend, new LongInvoker(target, variableName, + getFromMethod), ILongSource.Variables.Default); + else + throw new IllegalArgumentException("The target object " + target + + " does not provide a value of a valid data type."); + + } + + // -------------------------------------------------------------------------- + // + // Methods to specify colour of each bar (source) + // + // -------------------------------------------------------------------------- + + /** + * Build a series retrieving data from a IDoubleSource object, using the + * default variableId and specifying the colour. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IDoubleSource + * interface. + * @param color + * Specifies the color of the bar + */ + public void addSources(String legend, IDoubleSource plottableObject, Color color) { + addSources(legend, plottableObject); + int seriesNum = sources.size() - 1; // Start with value of 0 + colorMap.addColor(seriesNum, color); + } + + /** + * Build a series retrieving data from a IDoubleSource object and specifying the + * colour. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IDoubleSource + * interface. + * @param variableID + * The variable id of the source object. + * @param color + * Specifies the color of the bar + */ + public void addSources(String legend, IDoubleSource plottableObject, + Enum variableID, Color color) { + sources.add(new DSource(legend, plottableObject, variableID)); + categories.add(legend); + int seriesNum = sources.size() - 1; // Start with value of 0 + colorMap.addColor(seriesNum, color); + } + + /** + * Build a series from a IFloatSource object, using the default variableId and + * specifying the colour. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IFloatSource + * interface. + * @param color + * Specifies the color of the bar + */ + public void addSources(String legend, IFloatSource plottableObject, Color color) { + sources.add(new FSource(legend, plottableObject, IFloatSource.Variables.Default)); + categories.add(legend); + int seriesNum = sources.size() - 1; // Start with value of 0 + colorMap.addColor(seriesNum, color); + } + + /** + * Build a series from a IFloatSource object and specifying the colour. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IFloatSource + * interface. + * @param variableID + * The variable id of the source object. + * @param color + * Specifies the color of the bar + */ + public void addSources(String legend, IFloatSource plottableObject, + Enum variableID, Color color) { + sources.add(new FSource(legend, plottableObject, variableID)); + categories.add(legend); + int seriesNum = sources.size() - 1; // Start with value of 0 + colorMap.addColor(seriesNum, color); + } + + /** + * Build a series from a ILongSource object, using the default variableId and + * specifying the colour. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the ILongSource + * interface. + * @param color + * Specifies the color of the bar + */ + public void addSources(String legend, ILongSource plottableObject, Color color) { + sources.add(new LSource(legend, plottableObject, ILongSource.Variables.Default)); + categories.add(legend); + int seriesNum = sources.size() - 1; // Start with value of 0 + colorMap.addColor(seriesNum, color); + } + + /** + * Build a series from a ILongSource object and specifying the colour. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IDblSource + * interface. + * @param variableID + * The variable id of the source object. + * @param color + * Specifies the color of the bar + */ + public void addSources(String legend, ILongSource plottableObject, + Enum variableID, Color color) { + sources.add(new LSource(legend, plottableObject, variableID)); + categories.add(legend); + int seriesNum = sources.size() - 1; // Start with value of 0 + colorMap.addColor(seriesNum, color); + } + + /** + * Build a series from a IIntSource object, using the default variableId and + * specifying the colour. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IIntSource + * interface. + * @param color + * Specifies the color of the bar + */ + public void addSources(String legend, IIntSource plottableObject, Color color) { + sources.add(new ISource(legend, plottableObject, IIntSource.Variables.Default)); + categories.add(legend); + int seriesNum = sources.size() - 1; // Start with value of 0 + colorMap.addColor(seriesNum, color); + } + + /** + * Build a series from a IIntSource object and specifying the colour. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IIntSource + * interface. + * @param variableID + * The variable id of the source object. + * @param color + * Specifies the color of the bar + */ + public void addSources(String legend, IIntSource plottableObject, + Enum variableID, Color color) { + sources.add(new ISource(legend, plottableObject, variableID)); + categories.add(legend); + int seriesNum = sources.size() - 1; // Start with value of 0 + colorMap.addColor(seriesNum, color); + } + + /** + * Build a series from a generic object and specifying the colour. + * + * @param legend + * The legend name of the series. + * @param target + * The data source object. + * @param variableName + * The variable or method name of the source object. + * @param getFromMethod + * Specifies if the variableName is a field or a method. + * @param color + * Specifies the color of the bar + */ + public void addSources(String legend, Object target, String variableName, + boolean getFromMethod, Color color) { + addSources(legend, target, variableName, getFromMethod); + int seriesNum = sources.size() - 1; // Start with value of 0 + colorMap.addColor(seriesNum, color); + } + + class ColoredBarRenderer extends BarRenderer { + + private static final long serialVersionUID = -7678490515617294057L; + + private FixedColorMap colormap; + + ColoredBarRenderer(FixedColorMap colormap) { + this.colormap = colormap; + } + + public Paint getItemPaint(final int row, final int column) { + // returns color for each column + return (colormap.getColor(column)); + // return (colormap.getMappedColor(column)); + } + } +} diff --git a/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotter.java b/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotter.java new file mode 100644 index 00000000..4789115b --- /dev/null +++ b/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotter.java @@ -0,0 +1,764 @@ +package microsim.gui.plot; + +import java.awt.Color; +import java.util.ArrayList; + +import javax.swing.JInternalFrame; + +import microsim.event.CommonEventType; +import microsim.event.EventListener; +import microsim.reflection.ReflectionUtils; +import microsim.statistics.IDoubleSource; +import microsim.statistics.IFloatSource; +import microsim.statistics.IIntSource; +import microsim.statistics.ILongSource; +import microsim.statistics.IUpdatableSource; +import microsim.statistics.reflectors.DoubleInvoker; +import microsim.statistics.reflectors.FloatInvoker; +import microsim.statistics.reflectors.IntegerInvoker; +import microsim.statistics.reflectors.LongInvoker; + +import org.apache.commons.math3.util.Pair; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYItemRenderer; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +/** + * A ScatterplotSimulationPlotter is able to trace one or more pairs of data + * sources + * over time, creating a scatterplot chart. It is based on JFreeChart library + * and + * uses data sources based on the microsim.statistics.* interfaces.
+ * + * + *

+ * Title: JAS-mine + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2017 Ross Richardson + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Ross Richardson + *

+ */ +public class ScatterplotSimulationPlotter extends JInternalFrame implements EventListener { + + private static final long serialVersionUID = 1L; + + private ArrayList> sources; + + private XYSeriesCollection dataset; + + private int maxSamples; + + /** + * Constructor for scatterplot chart objects with chart legend displayed by + * default and + * all data samples shown, accumulating as time moves forward. If it is desired + * to turn + * the legend off, or set a limit to the number of previous time-steps of data + * displayed + * in the chart, use the constructor + * ScatterplotSimulationPlotter(String title, String xaxis, String yaxis, + * boolean includeLegend, int maxSamples) + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param yaxis - name of the y-axis + * + */ + public ScatterplotSimulationPlotter(String title, String xaxis, String yaxis) { // Includes legend by default and + // will accumulate data samples by + // default (if wanting only the most + // recent data points, use the other + // constructor) + this(title, xaxis, yaxis, true, 0); + } + + /** + * Constructor for scatterplot chart objects, featuring a toggle to hide the + * chart legend + * and to set the number of previous time-steps of data to display in the chart. + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param yaxis - name of the y-axis + * @param includeLegend - toggles whether to include the legend. If displaying a + * very large number of different series in the chart, it + * may be useful to turn + * the legend off as it will occupy a lot of space in the + * GUI. + * @param maxSamples - the number of 'snapshots' of data displayed in the + * chart. + * Only data from the last 'maxSamples' updates will be + * displayed in the chart, + * so if the chart is updated at each 'time-step', then + * only the most recent + * 'maxSamples' time-steps will be shown on the chart. If + * the user wishes to + * accumulate all data points from the simulation run, i.e. + * to display all + * available data from all previous time-steps, set this to + * 0. + */ + public ScatterplotSimulationPlotter(String title, String xaxis, String yaxis, boolean includeLegend, + int maxSamples) { // Can specify whether to include legend and how many samples (updates) to + // display + super(); + this.setResizable(true); + this.setTitle(title); + this.maxSamples = maxSamples; + + sources = new ArrayList>(); + + dataset = new XYSeriesCollection(); + + final JFreeChart chart = ChartFactory.createScatterPlot( + title, // chart title + xaxis, // x axis label + yaxis, // y axis label + dataset, // data + PlotOrientation.VERTICAL, + includeLegend, // include legend + true, // tooltips + false // urls + ); + + // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... + chart.setBackgroundPaint(Color.white); + + // get a reference to the plot for further customisation... + final XYPlot plot = chart.getXYPlot(); + plot.setBackgroundPaint(Color.lightGray); + plot.setDomainGridlinePaint(Color.white); + plot.setRangeGridlinePaint(Color.white); + + final XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true); // Shapes only + // renderer.setSeriesLinesVisible(0, false); + // renderer.setSeriesShapesVisible(1, false); + plot.setRenderer(renderer); + + final NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis(); + domainAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); + final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); + rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); + + final ChartPanel chartPanel = new ChartPanel(chart); + + chartPanel.setPreferredSize(new java.awt.Dimension(500, 270)); + + setContentPane(chartPanel); + + this.setSize(400, 400); + } + + public void onEvent(Enum type) { + if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) { + update(); + } + } + + public void update() { + double x = 0.0, y = 0.0; + for (int i = 0; i < sources.size(); i++) { + Source source_X = sources.get(i).getFirst(); + Source source_Y = sources.get(i).getSecond(); + XYSeries series = dataset.getSeries(i); + x = source_X.getDouble(); + y = source_Y.getDouble(); + series.add(x, y); + // if (maxSamples > 0 && series.getItemCount() > maxSamples ) { //Should no + // longer be necessary if using XYSeries.setMaximumItemCount() + // XYDataItem xy = series.remove(0); + // System.out.println(series.getItemCount() + ", (" + xy.getXValue() + ", " + + // xy.getYValue() + ")"); + // } + } + } + + private abstract class Source { + // public String label; + public Enum vId; + protected boolean isUpdatable; + + public abstract double getDouble(); + + // public String getLabel() { + // return label; + // } + // + // public void setLabel(String string) { + // label = string; + // } + + } + + private class DSource extends Source { + public IDoubleSource source; + + public DSource(String label, IDoubleSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getDoubleValue(vId); + } + } + + private class FSource extends Source { + public IFloatSource source; + + public FSource(String label, IFloatSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getFloatValue(vId); + } + } + + private class ISource extends Source { + public IIntSource source; + + public ISource(String label, IIntSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getIntValue(vId); + } + } + + private class LSource extends Source { + public ILongSource source; + + public LSource(String label, ILongSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getLongValue(vId); + } + } + + /** + * Build a series of paired values, retrieving data from two IDoubleSource + * objects, using the + * default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the + * IDoubleSource + * interface to produce values for the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the + * IDoubleSource + * interface to produce values for the y-axis (range). + */ + public void addSeries(String legend, IDoubleSource plottableObject_X, IDoubleSource plottableObject_Y) { + DSource sourceX = new DSource(legend, plottableObject_X, IDoubleSource.Variables.Default); + DSource sourceY = new DSource(legend, plottableObject_Y, IDoubleSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values, retrieving data from two IDoubleSource + * objects. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the + * IDoubleSource + * interface producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the + * IDoubleSource + * interface producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + */ + public void addSeries(String legend, IDoubleSource plottableObject_X, + Enum variableID_X, IDoubleSource plottableObject_Y, Enum variableID_Y) { + DSource sourceX = new DSource(legend, plottableObject_X, variableID_X); + DSource sourceY = new DSource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two IFloatSource objects, using the + * default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the IFloatSource + * interface to produce values for the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the IFloatSource + * interface to produce values for the y-axis (range). + * + */ + public void addSeries(String legend, IFloatSource plottableObject_X, IFloatSource plottableObject_Y) { + // sources.add(new FSource(legend, plottableObject, + // IFloatSource.Variables.Default)); + FSource sourceX = new FSource(legend, plottableObject_X, IFloatSource.Variables.Default); + FSource sourceY = new FSource(legend, plottableObject_Y, IFloatSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two IFloatSource objects. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the IFloatSource + * interface producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the IFloatSource + * interface producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + */ + public void addSeries(String legend, IFloatSource plottableObject_X, + Enum variableID_X, IFloatSource plottableObject_Y, Enum variableID_Y) { + FSource sourceX = new FSource(legend, plottableObject_X, variableID_X); + FSource sourceY = new FSource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two ILongSource objects, using the + * default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the ILongSource + * interface producing values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the ILongSource + * interface producing values of the y-axis (range). + */ + public void addSeries(String legend, ILongSource plottableObject_X, ILongSource plottableObject_Y) { + // sources.add(new LSource(legend, plottableObject, + // ILongSource.Variables.Default)); + LSource sourceX = new LSource(legend, plottableObject_X, ILongSource.Variables.Default); + LSource sourceY = new LSource(legend, plottableObject_Y, ILongSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two ILongSource objects + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the ILongSource + * interface producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the ILongSource + * interface producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + */ + public void addSeries(String legend, ILongSource plottableObject_X, + Enum variableID_X, ILongSource plottableObject_Y, Enum variableID_Y) { + LSource sourceX = new LSource(legend, plottableObject_X, variableID_X); + LSource sourceY = new LSource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two IIntSource objects, using the + * default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the IIntSource + * interface + * producing values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the IIntSource + * interface + * producing values of the y-axis (range). + */ + public void addSeries(String legend, IIntSource plottableObject_X, IIntSource plottableObject_Y) { + // sources.add(new ISource(legend, plottableObject, + // IIntSource.Variables.Default)); + ISource sourceX = new ISource(legend, plottableObject_X, IIntSource.Variables.Default); + ISource sourceY = new ISource(legend, plottableObject_Y, IIntSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two IIntSource objects. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the IIntSource + * interface + * producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the IIntSource + * interface + * producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + * + */ + public void addSeries(String legend, IIntSource plottableObject_X, + Enum variableID_X, IIntSource plottableObject_Y, Enum variableID_Y) { + // sources.add(new ISource(legend, plottableObject, variableID)); + ISource sourceX = new ISource(legend, plottableObject_X, variableID_X); + ISource sourceY = new ISource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two generic objects. + * + * @param legend + * The legend name of the series. + * @param target_X + * The data source object for x-axis values (domain). + * @param variableName_X + * The variable or method name of the source object + * producing + * values for the x-axis (domain). + * @param getFromMethod_X + * Specifies if the variableName_X is a field or a + * method. + * @param target_Y + * The data source object for y-axis values (range). + * @param variableName_Y + * The variable or method name of the source object + * producing + * values for the y-axis (range). + * @param getFromMethod_Y + * Specifies if the variableName_Y is a field or a + * method. + */ + public void addSeries(String legend, Object target_X, String variableName_X, + boolean getFromMethod_X, Object target_Y, String variableName_Y, + boolean getFromMethod_Y) { + + // First, look at X values + Source sourceX = null; + if (ReflectionUtils.isDoubleSource(target_X.getClass(), variableName_X, + getFromMethod_X)) + sourceX = new DSource(legend, new DoubleInvoker(target_X, + variableName_X, getFromMethod_X), IDoubleSource.Variables.Default); + else if (ReflectionUtils.isFloatSource(target_X.getClass(), variableName_X, + getFromMethod_X)) + sourceX = new FSource(legend, new FloatInvoker(target_X, variableName_X, + getFromMethod_X), IFloatSource.Variables.Default); + else if (ReflectionUtils.isIntSource(target_X.getClass(), variableName_X, + getFromMethod_X)) + sourceX = new ISource(legend, new IntegerInvoker(target_X, + variableName_X, getFromMethod_X), IIntSource.Variables.Default); + else if (ReflectionUtils.isLongSource(target_X.getClass(), variableName_X, + getFromMethod_X)) + sourceX = new LSource(legend, new LongInvoker(target_X, variableName_X, + getFromMethod_X), ILongSource.Variables.Default); + else + throw new IllegalArgumentException("The target_X object " + target_X + + " does not provide a value of a valid data type."); + + // Now for Y values + Source sourceY = null; + if (ReflectionUtils.isDoubleSource(target_Y.getClass(), variableName_Y, + getFromMethod_Y)) + sourceY = new DSource(legend, new DoubleInvoker(target_Y, + variableName_Y, getFromMethod_Y), IDoubleSource.Variables.Default); + else if (ReflectionUtils.isFloatSource(target_Y.getClass(), variableName_Y, + getFromMethod_Y)) + sourceY = new FSource(legend, new FloatInvoker(target_Y, variableName_Y, + getFromMethod_Y), IFloatSource.Variables.Default); + else if (ReflectionUtils.isIntSource(target_Y.getClass(), variableName_Y, + getFromMethod_Y)) + sourceY = new ISource(legend, new IntegerInvoker(target_Y, + variableName_Y, getFromMethod_Y), IIntSource.Variables.Default); + else if (ReflectionUtils.isLongSource(target_Y.getClass(), variableName_Y, + getFromMethod_Y)) + sourceY = new LSource(legend, new LongInvoker(target_Y, variableName_Y, + getFromMethod_Y), ILongSource.Variables.Default); + else + throw new IllegalArgumentException("The target_Y object " + target_Y + + " does not provide a value of a valid data type."); + + // sources.add(source); + sources.add(new Pair(sourceX, sourceY)); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + // plot.addLegend(sources.size() - 1, legend); + } + + /** + * Build a series of paired values, retrieving x-axis data from an IDoubleSource + * object and y-axis data + * from an ILongSource object, using the default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the + * IDoubleSource + * interface to produce values for the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the ILongSource + * interface to produce values for the y-axis (range). + */ + public void addSeries(String legend, IDoubleSource plottableObject_X, ILongSource plottableObject_Y) { + DSource sourceX = new DSource(legend, plottableObject_X, IDoubleSource.Variables.Default); + LSource sourceY = new LSource(legend, plottableObject_Y, ILongSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values, retrieving x-axis data from an IDoubleSource + * object and y-axis data + * from an ILongSource object. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the + * IDoubleSource + * interface producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the ILongSource + * interface producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + */ + public void addSeries(String legend, IDoubleSource plottableObject_X, + Enum variableID_X, ILongSource plottableObject_Y, Enum variableID_Y) { + DSource sourceX = new DSource(legend, plottableObject_X, variableID_X); + LSource sourceY = new LSource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values, retrieving x-axis data from an ILongSource + * object and + * y-axis data from an IDoubleSource object, using the default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the ILongSource + * interface to produce values for the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the + * IDoubleSource + * interface to produce values for the y-axis (range). + */ + public void addSeries(String legend, ILongSource plottableObject_X, IDoubleSource plottableObject_Y) { + LSource sourceX = new LSource(legend, plottableObject_X, ILongSource.Variables.Default); + DSource sourceY = new DSource(legend, plottableObject_Y, IDoubleSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values, retrieving x-axis data from an ILongSource + * object and + * y-axis data from an IDoubleSource object. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the ILongSource + * interface producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the + * IDoubleSource + * interface producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + */ + public void addSeries(String legend, ILongSource plottableObject_X, + Enum variableID_X, IDoubleSource plottableObject_Y, Enum variableID_Y) { + LSource sourceX = new LSource(legend, plottableObject_X, variableID_X); + DSource sourceY = new DSource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Max samples parameters allow to define a maximum number of time-steps used + * in the scatter plot. When set, the oldest data points are removed as time + * moves forward to maintain the number of samples (time-steps) in the chart. + */ + public int getMaxSamples() { + return maxSamples; + } + + /** + * Set the max sample parameter. + * + * @param maxSamples Maximum number of time-steps rendered on x axis. + */ + public void setMaxSamples(int maxSamples) { + this.maxSamples = maxSamples; + } + +} diff --git a/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotterRefreshable.java b/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotterRefreshable.java new file mode 100644 index 00000000..83613320 --- /dev/null +++ b/src/main/java/microsim/gui/plot/ScatterplotSimulationPlotterRefreshable.java @@ -0,0 +1,791 @@ +package microsim.gui.plot; + +//package microsim.gui.plot; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JInternalFrame; + +import microsim.event.CommonEventType; +import microsim.event.EventListener; +import microsim.reflection.ReflectionUtils; +import microsim.statistics.IDoubleSource; +import microsim.statistics.IFloatSource; +import microsim.statistics.IIntSource; +import microsim.statistics.ILongSource; +import microsim.statistics.IUpdatableSource; +import microsim.statistics.reflectors.DoubleInvoker; +import microsim.statistics.reflectors.FloatInvoker; +import microsim.statistics.reflectors.IntegerInvoker; +import microsim.statistics.reflectors.LongInvoker; + +import org.apache.commons.math3.util.Pair; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYItemRenderer; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +/** + * A ScatterplotSimulationPlotterRefreshable is able to trace one or more pairs + * of data sources + * between scheduled simulation time-steps, creating a scatterplot chart that is + * updated on + * demand (e.g. for visualising the progress of the simulation between + * time-steps). + * It is based on ScatterplotSimulationPlotter, which is itself based on + * JFreeChart library and + * uses data sources based on the microsim.statistics.* interfaces.
+ * + * + *

+ * Title: JAS-mine + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2017 Ross Richardson + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Ross Richardson + *

+ */ +public class ScatterplotSimulationPlotterRefreshable extends JInternalFrame implements EventListener { + + private static final long serialVersionUID = 1L; + + private ArrayList> sources; + + private XYSeriesCollection dataset; + + private int maxSamples; + + /** + * Constructor for scatterplot chart objects with chart legend displayed by + * default and + * all data samples shown, accumulating as time moves forward. If it is desired + * to turn + * the legend off, or set a limit to the number of previous time-steps of data + * displayed + * in the chart, use the constructor + * ScatterplotSimulationPlotter(String title, String xaxis, String yaxis, + * boolean includeLegend, int maxSamples) + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param yaxis - name of the y-axis + * + */ + public ScatterplotSimulationPlotterRefreshable(String title, String xaxis, String yaxis) { // Includes legend by + // default and will + // accumulate data + // samples by default (if + // wanting only the most + // recent data points, + // use the other + // constructor) + this(title, xaxis, yaxis, true, 0); + } + + /** + * Constructor for scatterplot chart objects, featuring a toggle to hide the + * chart legend + * and to set the number of previous time-steps of data to display in the chart. + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param yaxis - name of the y-axis + * @param includeLegend - toggles whether to include the legend. If displaying a + * very large number of different series in the chart, it + * may be useful to turn + * the legend off as it will occupy a lot of space in the + * GUI. + * @param maxSamples - the number of 'snapshots' of data displayed in the + * chart. + * Only data from the last 'maxSamples' updates will be + * displayed in the chart, + * so if the chart is updated at each 'time-step', then + * only the most recent + * 'maxSamples' time-steps will be shown on the chart. If + * the user wishes to + * accumulate all data points from the simulation run, i.e. + * to display all + * available data from all previous time-steps, set this to + * 0. + */ + public ScatterplotSimulationPlotterRefreshable(String title, String xaxis, String yaxis, boolean includeLegend, + int maxSamples) { // Can specify whether to include legend and how many samples (updates) to + // display + super(); + this.setResizable(true); + this.setTitle(title); + this.maxSamples = maxSamples; + + sources = new ArrayList>(); + + dataset = new XYSeriesCollection(); + + final JFreeChart chart = ChartFactory.createScatterPlot( + title, // chart title + xaxis, // x axis label + yaxis, // y axis label + dataset, // data + PlotOrientation.VERTICAL, + includeLegend, // include legend + true, // tooltips + false // urls + ); + + // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... + chart.setBackgroundPaint(Color.white); + + // get a reference to the plot for further customisation... + final XYPlot plot = chart.getXYPlot(); + plot.setBackgroundPaint(Color.lightGray); + plot.setDomainGridlinePaint(Color.white); + plot.setRangeGridlinePaint(Color.white); + + final XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true); // Shapes only + // renderer.setSeriesLinesVisible(0, false); + // renderer.setSeriesShapesVisible(1, false); + plot.setRenderer(renderer); + + final NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis(); + domainAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); + final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); + rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); + + final ChartPanel chartPanel = new ChartPanel(chart); + + chartPanel.setPreferredSize(new java.awt.Dimension(500, 270)); + + setContentPane(chartPanel); + + this.setSize(400, 400); + } + + public void onEvent(Enum type) { + if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) { + update(); + } + } + + public void update() { + double x = 0.0, y = 0.0; + for (int i = 0; i < sources.size(); i++) { + Source source_X = sources.get(i).getFirst(); + Source source_Y = sources.get(i).getSecond(); + XYSeries series = dataset.getSeries(i); + x = source_X.getDouble(); + y = source_Y.getDouble(); + series.add(x, y); + // if (maxSamples > 0 && series.getItemCount() > maxSamples ) { //Should no + // longer be necessary if using XYSeries.setMaximumItemCount() + // XYDataItem xy = series.remove(0); + // System.out.println(series.getItemCount() + ", (" + xy.getXValue() + ", " + + // xy.getYValue() + ")"); + // } + } + } + + private abstract class Source { + // public String label; + public Enum vId; + protected boolean isUpdatable; + + public abstract double getDouble(); + + // public String getLabel() { + // return label; + // } + // + // public void setLabel(String string) { + // label = string; + // } + + } + + private class DSource extends Source { + public IDoubleSource source; + + public DSource(String label, IDoubleSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getDoubleValue(vId); + } + } + + private class FSource extends Source { + public IFloatSource source; + + public FSource(String label, IFloatSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getFloatValue(vId); + } + } + + private class ISource extends Source { + public IIntSource source; + + public ISource(String label, IIntSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getIntValue(vId); + } + } + + private class LSource extends Source { + public ILongSource source; + + public LSource(String label, ILongSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getLongValue(vId); + } + } + + /** + * Build a series of paired values, retrieving data from two IDoubleSource + * objects, using the + * default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the + * IDoubleSource + * interface to produce values for the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the + * IDoubleSource + * interface to produce values for the y-axis (range). + */ + public void addSeries(String legend, IDoubleSource plottableObject_X, IDoubleSource plottableObject_Y) { + DSource sourceX = new DSource(legend, plottableObject_X, IDoubleSource.Variables.Default); + DSource sourceY = new DSource(legend, plottableObject_Y, IDoubleSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values, retrieving data from two IDoubleSource + * objects. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the + * IDoubleSource + * interface producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the + * IDoubleSource + * interface producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + */ + public void addSeries(String legend, IDoubleSource plottableObject_X, + Enum variableID_X, IDoubleSource plottableObject_Y, Enum variableID_Y) { + DSource sourceX = new DSource(legend, plottableObject_X, variableID_X); + DSource sourceY = new DSource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two IFloatSource objects, using the + * default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the IFloatSource + * interface to produce values for the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the IFloatSource + * interface to produce values for the y-axis (range). + * + */ + public void addSeries(String legend, IFloatSource plottableObject_X, IFloatSource plottableObject_Y) { + // sources.add(new FSource(legend, plottableObject, + // IFloatSource.Variables.Default)); + FSource sourceX = new FSource(legend, plottableObject_X, IFloatSource.Variables.Default); + FSource sourceY = new FSource(legend, plottableObject_Y, IFloatSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two IFloatSource objects. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the IFloatSource + * interface producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the IFloatSource + * interface producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + */ + public void addSeries(String legend, IFloatSource plottableObject_X, + Enum variableID_X, IFloatSource plottableObject_Y, Enum variableID_Y) { + FSource sourceX = new FSource(legend, plottableObject_X, variableID_X); + FSource sourceY = new FSource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two ILongSource objects, using the + * default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the ILongSource + * interface producing values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the ILongSource + * interface producing values of the y-axis (range). + */ + public void addSeries(String legend, ILongSource plottableObject_X, ILongSource plottableObject_Y) { + // sources.add(new LSource(legend, plottableObject, + // ILongSource.Variables.Default)); + LSource sourceX = new LSource(legend, plottableObject_X, ILongSource.Variables.Default); + LSource sourceY = new LSource(legend, plottableObject_Y, ILongSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two ILongSource objects + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the ILongSource + * interface producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the ILongSource + * interface producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + */ + public void addSeries(String legend, ILongSource plottableObject_X, + Enum variableID_X, ILongSource plottableObject_Y, Enum variableID_Y) { + LSource sourceX = new LSource(legend, plottableObject_X, variableID_X); + LSource sourceY = new LSource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two IIntSource objects, using the + * default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the IIntSource + * interface + * producing values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the IIntSource + * interface + * producing values of the y-axis (range). + */ + public void addSeries(String legend, IIntSource plottableObject_X, IIntSource plottableObject_Y) { + // sources.add(new ISource(legend, plottableObject, + // IIntSource.Variables.Default)); + ISource sourceX = new ISource(legend, plottableObject_X, IIntSource.Variables.Default); + ISource sourceY = new ISource(legend, plottableObject_Y, IIntSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two IIntSource objects. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the IIntSource + * interface + * producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the IIntSource + * interface + * producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + * + */ + public void addSeries(String legend, IIntSource plottableObject_X, + Enum variableID_X, IIntSource plottableObject_Y, Enum variableID_Y) { + // sources.add(new ISource(legend, plottableObject, variableID)); + ISource sourceX = new ISource(legend, plottableObject_X, variableID_X); + ISource sourceY = new ISource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values from two generic objects. + * + * @param legend + * The legend name of the series. + * @param target_X + * The data source object for x-axis values (domain). + * @param variableName_X + * The variable or method name of the source object + * producing + * values for the x-axis (domain). + * @param getFromMethod_X + * Specifies if the variableName_X is a field or a + * method. + * @param target_Y + * The data source object for y-axis values (range). + * @param variableName_Y + * The variable or method name of the source object + * producing + * values for the y-axis (range). + * @param getFromMethod_Y + * Specifies if the variableName_Y is a field or a + * method. + */ + public void addSeries(String legend, Object target_X, String variableName_X, + boolean getFromMethod_X, Object target_Y, String variableName_Y, + boolean getFromMethod_Y) { + + // First, look at X values + Source sourceX = null; + if (ReflectionUtils.isDoubleSource(target_X.getClass(), variableName_X, + getFromMethod_X)) + sourceX = new DSource(legend, new DoubleInvoker(target_X, + variableName_X, getFromMethod_X), IDoubleSource.Variables.Default); + else if (ReflectionUtils.isFloatSource(target_X.getClass(), variableName_X, + getFromMethod_X)) + sourceX = new FSource(legend, new FloatInvoker(target_X, variableName_X, + getFromMethod_X), IFloatSource.Variables.Default); + else if (ReflectionUtils.isIntSource(target_X.getClass(), variableName_X, + getFromMethod_X)) + sourceX = new ISource(legend, new IntegerInvoker(target_X, + variableName_X, getFromMethod_X), IIntSource.Variables.Default); + else if (ReflectionUtils.isLongSource(target_X.getClass(), variableName_X, + getFromMethod_X)) + sourceX = new LSource(legend, new LongInvoker(target_X, variableName_X, + getFromMethod_X), ILongSource.Variables.Default); + else + throw new IllegalArgumentException("The target_X object " + target_X + + " does not provide a value of a valid data type."); + + // Now for Y values + Source sourceY = null; + if (ReflectionUtils.isDoubleSource(target_Y.getClass(), variableName_Y, + getFromMethod_Y)) + sourceY = new DSource(legend, new DoubleInvoker(target_Y, + variableName_Y, getFromMethod_Y), IDoubleSource.Variables.Default); + else if (ReflectionUtils.isFloatSource(target_Y.getClass(), variableName_Y, + getFromMethod_Y)) + sourceY = new FSource(legend, new FloatInvoker(target_Y, variableName_Y, + getFromMethod_Y), IFloatSource.Variables.Default); + else if (ReflectionUtils.isIntSource(target_Y.getClass(), variableName_Y, + getFromMethod_Y)) + sourceY = new ISource(legend, new IntegerInvoker(target_Y, + variableName_Y, getFromMethod_Y), IIntSource.Variables.Default); + else if (ReflectionUtils.isLongSource(target_Y.getClass(), variableName_Y, + getFromMethod_Y)) + sourceY = new LSource(legend, new LongInvoker(target_Y, variableName_Y, + getFromMethod_Y), ILongSource.Variables.Default); + else + throw new IllegalArgumentException("The target_Y object " + target_Y + + " does not provide a value of a valid data type."); + + // sources.add(source); + sources.add(new Pair(sourceX, sourceY)); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + // plot.addLegend(sources.size() - 1, legend); + } + + /** + * Build a series of paired values, retrieving x-axis data from an IDoubleSource + * object and y-axis data + * from an ILongSource object, using the default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the + * IDoubleSource + * interface to produce values for the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the ILongSource + * interface to produce values for the y-axis (range). + */ + public void addSeries(String legend, IDoubleSource plottableObject_X, ILongSource plottableObject_Y) { + DSource sourceX = new DSource(legend, plottableObject_X, IDoubleSource.Variables.Default); + LSource sourceY = new LSource(legend, plottableObject_Y, ILongSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values, retrieving x-axis data from an IDoubleSource + * object and y-axis data + * from an ILongSource object. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the + * IDoubleSource + * interface producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the ILongSource + * interface producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + */ + public void addSeries(String legend, IDoubleSource plottableObject_X, + Enum variableID_X, ILongSource plottableObject_Y, Enum variableID_Y) { + DSource sourceX = new DSource(legend, plottableObject_X, variableID_X); + LSource sourceY = new LSource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values, retrieving x-axis data from an ILongSource + * object and + * y-axis data from an IDoubleSource object, using the default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the ILongSource + * interface to produce values for the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the + * IDoubleSource + * interface to produce values for the y-axis (range). + */ + public void addSeries(String legend, ILongSource plottableObject_X, IDoubleSource plottableObject_Y) { + LSource sourceX = new LSource(legend, plottableObject_X, ILongSource.Variables.Default); + DSource sourceY = new DSource(legend, plottableObject_Y, IDoubleSource.Variables.Default); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series of paired values, retrieving x-axis data from an ILongSource + * object and + * y-axis data from an IDoubleSource object. + * + * @param legend + * The legend name of the series. + * @param plottableObject_X + * The data source object implementing the ILongSource + * interface producing values of the x-axis (domain). + * @param variableID_X + * The variable id of the source object producing + * values of the x-axis (domain). + * @param plottableObject_Y + * The data source object implementing the + * IDoubleSource + * interface producing values of the y-axis (range). + * @param variableID_Y + * The variable id of the source object producing + * values of the y-axis (range). + */ + public void addSeries(String legend, ILongSource plottableObject_X, + Enum variableID_X, IDoubleSource plottableObject_Y, Enum variableID_Y) { + LSource sourceX = new LSource(legend, plottableObject_X, variableID_X); + DSource sourceY = new DSource(legend, plottableObject_Y, variableID_Y); + sources.add(new Pair(sourceX, sourceY)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Max samples parameters allow to define a maximum number of time-steps used + * in the scatter plot. When set, the oldest data points are removed as time + * moves forward to maintain the number of samples (time-steps) in the chart. + */ + public int getMaxSamples() { + return maxSamples; + } + + /** + * Set the max sample parameter. + * + * @param maxSamples Maximum number of time-steps rendered on x axis. + */ + public void setMaxSamples(int maxSamples) { + this.maxSamples = maxSamples; + } + + public void refresh() { + + List data = dataset.getSeries(); + for (int i = 0; i < data.size(); i++) { + XYSeries series = (XYSeries) data.get(i); + series.clear(); + } + + } + + public void reset() { + + dataset.removeAllSeries(); + sources.clear(); + + } + +} diff --git a/src/main/java/microsim/gui/plot/TimeSeriesSimulationPlotter.java b/src/main/java/microsim/gui/plot/TimeSeriesSimulationPlotter.java new file mode 100644 index 00000000..74386e3e --- /dev/null +++ b/src/main/java/microsim/gui/plot/TimeSeriesSimulationPlotter.java @@ -0,0 +1,550 @@ +package microsim.gui.plot; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; + +import javax.swing.JInternalFrame; + +import microsim.engine.SimulationEngine; +import microsim.event.CommonEventType; +import microsim.event.EventListener; +import microsim.reflection.ReflectionUtils; +import microsim.statistics.IDoubleSource; +import microsim.statistics.IFloatSource; +import microsim.statistics.IIntSource; +import microsim.statistics.ILongSource; +import microsim.statistics.IUpdatableSource; +import microsim.statistics.reflectors.DoubleInvoker; +import microsim.statistics.reflectors.FloatInvoker; +import microsim.statistics.reflectors.IntegerInvoker; +import microsim.statistics.reflectors.LongInvoker; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +/** + * A time series plotter is able to trace one or more data sources over time. It + * is based on JFreeChart library and uses data sources based on the + * microsim.statistics.* interfaces.
+ * + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002-17 Michele Sonnessa and Ross Richardson + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa and Ross Richardson + *

+ */ +public class TimeSeriesSimulationPlotter extends JInternalFrame implements EventListener { + + private static final long serialVersionUID = 1L; + + private ArrayList sources; + + private XYSeriesCollection dataset; + + private XYPlot plot; + + private XYLineAndShapeRenderer renderer; + + private int maxSamples = 0; + + public TimeSeriesSimulationPlotter(String title, String yaxis) { // Include legend by default + this(title, yaxis, true, 0); + } + + public TimeSeriesSimulationPlotter(String title, String yaxis, boolean includeLegend, int maxSamples) { // Can + // specify + // whether + // to + // include + // legend + super(); + this.setResizable(true); + this.setTitle(title); + this.maxSamples = maxSamples; + + sources = new ArrayList(); + + dataset = new XYSeriesCollection(); + + final JFreeChart chart = ChartFactory.createXYLineChart( + title, // chart title + "Simulation time", // x axis label + yaxis, // y axis label + dataset, // data + PlotOrientation.VERTICAL, + includeLegend, // include legend + true, // tooltips + false // urls + ); + + // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... + chart.setBackgroundPaint(Color.white); + + // String fontName = chart.getLegend().getItemFont().getFontName(); + // int style = chart.getLegend().getItemFont().getStyle(); + // int size = chart.getLegend().getItemFont().getSize(); + // chart.getLegend().setItemFont(new Font(fontName, style, + // (int)MicrosimShell.scale*size)); + + // get a reference to the plot for further customisation... + plot = chart.getXYPlot(); + plot.setBackgroundPaint(Color.lightGray); + plot.setDomainGridlinePaint(Color.white); + plot.setRangeGridlinePaint(Color.white); + + renderer = new XYLineAndShapeRenderer(); + // renderer.setSeriesLinesVisible(0, false); + // renderer.setSeriesShapesVisible(1, false); + + plot.setRenderer(renderer); + + // change the auto tick unit selection to integer units only... + final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); + // rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); // Ross - made this change to allow units + // on Y axis for finer ticks, which is + // especially important for timeseries + // with values < 1. + + final ChartPanel chartPanel = new ChartPanel(chart); + + chartPanel.setPreferredSize(new java.awt.Dimension(500, 270)); + + setContentPane(chartPanel); + + this.setSize(400, 400); + } + + public void onEvent(Enum type) { + if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) { + double d = 0.0; + for (int i = 0; i < sources.size(); i++) { + Source source = sources.get(i); + XYSeries series = dataset.getSeries(i); + d = source.getDouble(); + series.add(SimulationEngine.getInstance().getTime(), d); + // if (maxSamples > 0 && series.getItemCount() > maxSamples ) { + // series.remove(0); + // } + } + } + } + + private abstract class Source { + // public String label; + public Enum vId; + protected boolean isUpdatable; + + public abstract double getDouble(); + + // public String getLabel() { + // return label; + // } + // + // public void setLabel(String string) { + // label = string; + // } + + } + + private class DSource extends Source { + public IDoubleSource source; + + public DSource(String label, IDoubleSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getDoubleValue(vId); + } + } + + private class FSource extends Source { + public IFloatSource source; + + public FSource(String label, IFloatSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getFloatValue(vId); + } + } + + private class ISource extends Source { + public IIntSource source; + + public ISource(String label, IIntSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getIntValue(vId); + } + } + + private class LSource extends Source { + public ILongSource source; + + public LSource(String label, ILongSource source, Enum varId) { + // super.label = label; + this.source = source; + super.vId = varId; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double getDouble() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getLongValue(vId); + } + } + + /** + * Build a series retrieving data from a IDoubleSource object, using the + * default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IDoubleSource + * interface. + */ + public void addSeries(String legend, IDoubleSource plottableObject) { + sources.add(new DSource(legend, plottableObject, IDoubleSource.Variables.Default)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + public void addSeries(String legend, IDoubleSource plottableObject, Color lineColor, boolean shapesFilled, + boolean isDashed, Shape shape) { + sources.add(new DSource(legend, plottableObject, IDoubleSource.Variables.Default)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + + int seriesIndex = dataset.getSeriesIndex(series.getKey()); // Get int Index of series using its key + Stroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, + new float[] { 10.0f }, 0.0f); + getRenderer().setSeriesPaint(seriesIndex, lineColor); // Set color of the series in the renderer to what was + // requested + getRenderer().setSeriesShapesFilled(seriesIndex, shapesFilled); // Set if shapes should be filled or not + if (isDashed) { + getRenderer().setSeriesStroke(seriesIndex, dashed); + } + getRenderer().setSeriesShape(seriesIndex, shape); + } + + /** + * Build a series retrieving data from a IDoubleSource object. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IDoubleSource + * interface. + * @param variableID + * The variable id of the source object. + */ + public void addSeries(String legend, IDoubleSource plottableObject, + Enum variableID) { + sources.add(new DSource(legend, plottableObject, variableID)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + public void addSeries(String legend, IDoubleSource plottableObject, Enum variableID, Color lineColor, + boolean shapesFilled, boolean isDashed, Shape shape) { + sources.add(new DSource(legend, plottableObject, variableID)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + int seriesIndex = dataset.getSeriesIndex(series.getKey()); // Get int Index of series using its key + + Stroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, + new float[] { 10.0f }, 0.0f); + getRenderer().setSeriesPaint(seriesIndex, lineColor); // Set color of the series in the renderer to what was + // requested + getRenderer().setSeriesShapesFilled(seriesIndex, shapesFilled); // Set if shapes should be filled or not + if (isDashed) { + getRenderer().setSeriesStroke(seriesIndex, dashed); + } + getRenderer().setSeriesShape(seriesIndex, shape); + + } + + public void addSeries(String legend, IDoubleSource plottableObject, Enum variableID, Color lineColor, + boolean validation) { + if (validation) { + Shape myRectangle = new Rectangle2D.Float(-3, -3, 6, 6); + addSeries(legend, plottableObject, variableID, lineColor, false, true, myRectangle); + } else { + Shape myCircle = new Ellipse2D.Float(-3, -3, 6, 6); + addSeries(legend, plottableObject, lineColor, true, false, myCircle); + + } + + } + + /** + * Build a series from a IFloatSource object, using the default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IFloatSource + * interface. + */ + public void addSeries(String legend, IFloatSource plottableObject) { + sources.add(new FSource(legend, plottableObject, IFloatSource.Variables.Default)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series from a IFloatSource object. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IFloatSource + * interface. + * @param variableID + * The variable id of the source object. + */ + public void addSeries(String legend, IFloatSource plottableObject, + Enum variableID) { + sources.add(new FSource(legend, plottableObject, variableID)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series from a ILongSource object, using the default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the ILongSource + * interface. + */ + public void addSeries(String legend, ILongSource plottableObject) { + sources.add(new LSource(legend, plottableObject, ILongSource.Variables.Default)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series from a ILongSource object. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IDblSource + * interface. + * @param variableID + * The variable id of the source object. + */ + public void addSeries(String legend, ILongSource plottableObject, + Enum variableID) { + sources.add(new LSource(legend, plottableObject, variableID)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series from a IIntSource object, using the default variableId. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IIntSource + * interface. + */ + public void addSeries(String legend, IIntSource plottableObject) { + sources.add(new ISource(legend, plottableObject, IIntSource.Variables.Default)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series from a IIntSource object. + * + * @param legend + * The legend name of the series. + * @param plottableObject + * The data source object implementing the IIntSource + * interface. + * @param variableID + * The variable id of the source object. + */ + public void addSeries(String legend, IIntSource plottableObject, + Enum variableID) { + sources.add(new ISource(legend, plottableObject, variableID)); + // plot.addLegend(sources.size() - 1, legend); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + } + + /** + * Build a series from a generic object. + * + * @param legend + * The legend name of the series. + * @param target + * The data source object. + * @param variableName + * The variable or method name of the source object. + * @param getFromMethod + * Specifies if the variableName is a field or a method. + */ + public void addSeries(String legend, Object target, String variableName, + boolean getFromMethod) { + Source source = null; + if (ReflectionUtils.isDoubleSource(target.getClass(), variableName, + getFromMethod)) + source = new DSource(legend, new DoubleInvoker(target, + variableName, getFromMethod), IDoubleSource.Variables.Default); + else if (ReflectionUtils.isFloatSource(target.getClass(), variableName, + getFromMethod)) + source = new FSource(legend, new FloatInvoker(target, variableName, + getFromMethod), IFloatSource.Variables.Default); + else if (ReflectionUtils.isIntSource(target.getClass(), variableName, + getFromMethod)) + source = new ISource(legend, new IntegerInvoker(target, + variableName, getFromMethod), IIntSource.Variables.Default); + else if (ReflectionUtils.isLongSource(target.getClass(), variableName, + getFromMethod)) + source = new LSource(legend, new LongInvoker(target, variableName, + getFromMethod), ILongSource.Variables.Default); + else + throw new IllegalArgumentException("The target object " + target + + " does not provide a value of a valid data type."); + + sources.add(source); + XYSeries series = new XYSeries(legend); + if (maxSamples > 0) + series.setMaximumItemCount(maxSamples); + dataset.addSeries(series); + // plot.addLegend(sources.size() - 1, legend); + } + + /** + * Max samples parameters allow to define a maximum number of points. + * When set the plotting window shifts automatically along with time. + */ + public int getMaxSamples() { + return maxSamples; + } + + /** + * Set the max sample parameter. + * + * @param maxSamples Maximum number of time-steps rendered on x axis. + */ + public void setMaxSamples(int maxSamples) { + this.maxSamples = maxSamples; + } + + public XYLineAndShapeRenderer getRenderer() { + return (XYLineAndShapeRenderer) plot.getRenderer(); + } + + public void setRenderer(XYLineAndShapeRenderer renderer) { + plot.setRenderer(renderer); + } + +} diff --git a/src/main/java/microsim/gui/plot/Weighted_HistogramBin.java b/src/main/java/microsim/gui/plot/Weighted_HistogramBin.java new file mode 100644 index 00000000..c0f2a248 --- /dev/null +++ b/src/main/java/microsim/gui/plot/Weighted_HistogramBin.java @@ -0,0 +1,159 @@ +package microsim.gui.plot; + +/* Based on JFreeChart : a free chart library for the Java(tm) platform +* =========================================================== +* +* (C) Copyright 2017 by Ross Richardson +* +* Based on JFreeChart's HistogramBin by Object Refinery Limited and Contributors. +* +* Project Info: http://www.jfree.org/jfreechart/index.html +* +* This library is free software; you can redistribute it and/or modify it +* under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2.1 of the License, or +* (at your option) any later version. +* +* This library 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 Lesser General Public +* License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +* USA. +* +* [Oracle and Java are registered trademarks of Oracle and/or its affiliates. +* Other names may be trademarks of their respective owners.] +* +* ----------------- +* Weighted_HistogramBin.java +* ----------------- +* (C) Copyright 2017, by Ross Richardson +* Based on JFreeChart's HistogramBin by Jelai Wang and Contributors. +* +* Original Author: Jelai Wang (jelaiw AT mindspring.com); +* Contributor(s): David Gilbert (for Object Refinery Limited); +* +*/ + +import java.io.Serializable; + +/** + * A bin for the {@link Weighted_HistogramDataset} class. + */ +public class Weighted_HistogramBin implements Cloneable, Serializable { + + /** For serialisation. */ + private static final long serialVersionUID = 7614685080015589931L; + + /** + * The (weighted) number of items in the bin, the weights can be double, + * meaning that the count can also be double. + **/ + private double count; + + /** The start boundary. */ + private double startBoundary; + + /** The end boundary. */ + private double endBoundary; + + /** + * Creates a new bin. + * + * @param startBoundary the start boundary. + * @param endBoundary the end boundary. + */ + public Weighted_HistogramBin(double startBoundary, double endBoundary) { + if (startBoundary > endBoundary) { + throw new IllegalArgumentException( + "HistogramBin(): startBoundary > endBoundary."); + } + this.count = 0.; + this.startBoundary = startBoundary; + this.endBoundary = endBoundary; + } + + /** + * Returns the number of items in the bin. + * + * @return The item count. + */ + public double getCount() { + return this.count; + } + + /** + * Increments the item count. + */ + public void incrementCount(double weight) { + this.count += weight; + } + + /** + * Returns the start boundary. + * + * @return The start boundary. + */ + public double getStartBoundary() { + return this.startBoundary; + } + + /** + * Returns the end boundary. + * + * @return The end boundary. + */ + public double getEndBoundary() { + return this.endBoundary; + } + + /** + * Returns the bin width. + * + * @return The bin width. + */ + public double getBinWidth() { + return this.endBoundary - this.startBoundary; + } + + /** + * Tests this object for equality with an arbitrary object. + * + * @param obj the object to test against. + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj instanceof Weighted_HistogramBin) { + Weighted_HistogramBin bin = (Weighted_HistogramBin) obj; + boolean b0 = bin.startBoundary == this.startBoundary; + boolean b1 = bin.endBoundary == this.endBoundary; + boolean b2 = bin.count == this.count; + return b0 && b1 && b2; + } + return false; + } + + /** + * Returns a clone of the bin. + * + * @return A clone. + * + * @throws CloneNotSupportedException not thrown by this class. + */ + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} diff --git a/src/main/java/microsim/gui/plot/Weighted_HistogramDataset.java b/src/main/java/microsim/gui/plot/Weighted_HistogramDataset.java new file mode 100644 index 00000000..d0b762fc --- /dev/null +++ b/src/main/java/microsim/gui/plot/Weighted_HistogramDataset.java @@ -0,0 +1,504 @@ +package microsim.gui.plot; + +/* (C) Copyright 2017, by Ross Richardson based on JFreeChart's + * HistogramDataset.java by Object Refinery Limited and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * --------------------- + * Weighted_HistogramDataset.java + * --------------------- + * (C) Copyright 2017, by Ross Richardson + * + * Based on JFreeChart's HistogramDataset.java: + * (C) Copyright 2003-2013, by Jelai Wang and Contributors. + * + * Original Author: Jelai Wang (jelaiw AT mindspring.com); + * Contributor(s): David Gilbert (for Object Refinery Limited); + * Cameron Hayne; + * Rikard Bj?rklind; + * Thomas A Caswell (patch 2902842); + * + * + */ + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.data.general.DatasetChangeEvent; +import org.jfree.data.statistics.HistogramType; +import org.jfree.data.xy.AbstractIntervalXYDataset; +import org.jfree.data.xy.IntervalXYDataset; + +/** + * A weighted dataset that can be used for creating weighted histograms. + */ +public class Weighted_HistogramDataset extends AbstractIntervalXYDataset + implements IntervalXYDataset, Cloneable, PublicCloneable, + Serializable { + /** For serialization. */ + private static final long serialVersionUID = -6341668077370231153L; + + /** A list of maps. */ + private List list; + + /** The histogram type. */ + private HistogramType type; + + /** + * Sum of the total weightings of each value, in order to correctly calculate + * the chart for RELATIVE_FREQUENCY type. + */ + private double totalWeight; + + /** + * Creates a new (empty) dataset with a default type of + * {@link HistogramType}.FREQUENCY. + */ + public Weighted_HistogramDataset() { + this.list = new ArrayList(); + this.type = HistogramType.FREQUENCY; + totalWeight = 0.; + } + + /** + * Returns the histogram type. + * + * @return The type (never null). + */ + public HistogramType getType() { + return this.type; + } + + /** + * Sets the histogram type and sends a {@link DatasetChangeEvent} to all + * registered listeners. + * + * @param type the type (null not permitted). + */ + public void setType(HistogramType type) { + Args.nullNotPermitted(type, "type"); + this.type = type; + fireDatasetChanged(); + } + + /** + * Adds a series to the dataset, using the specified number of bins, + * and sends a {@link DatasetChangeEvent} to all registered listeners. + * + * @param key the series key (null not permitted). + * @param values the values (null not permitted). + * @param weightings the weights associated with the values, i.e. + * weight i indicates the number of times the value i appears + * (null not permitted). + * @param bins the number of bins (must be at least 1). + */ + public void addSeries(Comparable key, double[] values, double[] weightings, int bins) { + // defer argument checking... + double minimum = getMinimum(values); + double maximum = getMaximum(values); + addSeries(key, values, weightings, bins, minimum, maximum); + } + + /** + * Adds a series to the dataset. Any data value less than minimum will be + * assigned to the first bin, and any data value greater than maximum will + * be assigned to the last bin. Values falling on the boundary of + * adjacent bins will be assigned to the higher indexed bin. + * + * @param key the series key (null not permitted). + * @param values the raw observations. + * @param weightings the weights associated with the values, i.e. + * weight i indicates the number of times the value i appears + * (null not permitted). + * @param bins the number of bins (must be at least 1). + * @param minimum the lower bound of the bin range. + * @param maximum the upper bound of the bin range. + */ + public void addSeries(Comparable key, double[] values, double[] weightings, int bins, + double minimum, double maximum) { + + Args.nullNotPermitted(key, "key"); + Args.nullNotPermitted(values, "values"); + Args.nullNotPermitted(weightings, "weightings"); + if (bins < 1) { + throw new IllegalArgumentException( + "The 'bins' value must be at least 1."); + } + if (values.length != weightings.length) { + throw new IllegalArgumentException( + "The length of weightings array must be the same as the values array!"); + } + + double binWidth = (maximum - minimum) / bins; + + double lower = minimum; + double upper; + List binList = new ArrayList(bins); + for (int i = 0; i < bins; i++) { + Weighted_HistogramBin bin; + // make sure bins[bins.length]'s upper boundary ends at maximum + // to avoid the rounding issue. the bins[0] lower boundary is + // guaranteed start from min + if (i == bins - 1) { + bin = new Weighted_HistogramBin(lower, maximum); + } else { + upper = minimum + (i + 1) * binWidth; + bin = new Weighted_HistogramBin(lower, upper); + lower = upper; + } + binList.add(bin); + } + // fill the bins + for (int i = 0; i < values.length; i++) { + int binIndex = bins - 1; + if (values[i] < maximum) { + double fraction = (values[i] - minimum) / (maximum - minimum); + if (fraction < 0.0) { + fraction = 0.0; + } + binIndex = (int) (fraction * bins); + // rounding could result in binIndex being equal to bins + // which will cause an IndexOutOfBoundsException - see bug + // report 1553088 + if (binIndex >= bins) { + binIndex = bins - 1; + } + } + Weighted_HistogramBin bin = (Weighted_HistogramBin) binList.get(binIndex); + bin.incrementCount(weightings[i]); + totalWeight += weightings[i]; + } + // generic map for each series + Map map = new LinkedHashMap(); + map.put("key", key); + map.put("bins", binList); + map.put("values.length", new Integer(values.length)); + map.put("bin width", new Double(binWidth)); + this.list.add(map); + fireDatasetChanged(); + } + + /** + * Returns the minimum value in an array of values. + * + * @param values the values (null not permitted and + * zero-length array not permitted). + * + * @return The minimum value. + */ + private double getMinimum(double[] values) { + if (values == null || values.length < 1) { + throw new IllegalArgumentException( + "Null or zero length 'values' argument."); + } + double min = Double.MAX_VALUE; + for (int i = 0; i < values.length; i++) { + if (values[i] < min) { + min = values[i]; + } + } + return min; + } + + /** + * Returns the maximum value in an array of values. + * + * @param values the values (null not permitted and + * zero-length array not permitted). + * + * @return The maximum value. + */ + private double getMaximum(double[] values) { + if (values == null || values.length < 1) { + throw new IllegalArgumentException( + "Null or zero length 'values' argument."); + } + double max = -Double.MAX_VALUE; + for (int i = 0; i < values.length; i++) { + if (values[i] > max) { + max = values[i]; + } + } + return max; + } + + /** + * Returns the bins for a series. + * + * @param series the series index (in the range 0 to + * getSeriesCount() - 1). + * + * @return A list of bins. + * + * @throws IndexOutOfBoundsException if series is outside the + * specified range. + */ + List getBins(int series) { + Map map = (Map) this.list.get(series); + return (List) map.get("bins"); + } + + /** + * Returns the total number of observations for a series. + * + * @param series the series index. + * + * @return The total. + */ + private int getTotal(int series) { + Map map = (Map) this.list.get(series); + return ((Integer) map.get("values.length")).intValue(); + } + + /** + * Returns the bin width for a series. + * + * @param series the series index (zero based). + * + * @return The bin width. + */ + private double getBinWidth(int series) { + Map map = (Map) this.list.get(series); + return ((Double) map.get("bin width")).doubleValue(); + } + + /** + * Returns the number of series in the dataset. + * + * @return The series count. + */ + @Override + public int getSeriesCount() { + return this.list.size(); + } + + /** + * Returns the key for a series. + * + * @param series the series index (in the range 0 to + * getSeriesCount() - 1). + * + * @return The series key. + * + * @throws IndexOutOfBoundsException if series is outside the + * specified range. + */ + @Override + public Comparable getSeriesKey(int series) { + Map map = (Map) this.list.get(series); + return (Comparable) map.get("key"); + } + + /** + * Returns the number of data items for a series. + * + * @param series the series index (in the range 0 to + * getSeriesCount() - 1). + * + * @return The item count. + * + * @throws IndexOutOfBoundsException if series is outside the + * specified range. + */ + @Override + public int getItemCount(int series) { + return getBins(series).size(); + } + + /** + * Returns the X value for a bin. This value won't be used for plotting + * histograms, since the renderer will ignore it. But other renderers can + * use it (for example, you could use the dataset to create a line + * chart). + * + * @param series the series index (in the range 0 to + * getSeriesCount() - 1). + * @param item the item index (zero based). + * + * @return The start value. + * + * @throws IndexOutOfBoundsException if series is outside the + * specified range. + */ + @Override + public Number getX(int series, int item) { + List bins = getBins(series); + Weighted_HistogramBin bin = (Weighted_HistogramBin) bins.get(item); + double x = (bin.getStartBoundary() + bin.getEndBoundary()) / 2.; + return new Double(x); + } + + /** + * Returns the y-value for a bin (calculated to take into account the + * histogram type). + * + * @param series the series index (in the range 0 to + * getSeriesCount() - 1). + * @param item the item index (zero based). + * + * @return The y-value. + * + * @throws IndexOutOfBoundsException if series is outside the + * specified range. + */ + @Override + public Number getY(int series, int item) { + List bins = getBins(series); + Weighted_HistogramBin bin = (Weighted_HistogramBin) bins.get(item); + double total = getTotal(series); + double binWidth = getBinWidth(series); + + if (this.type == HistogramType.FREQUENCY) { + return new Double(bin.getCount()); + } else if (this.type == HistogramType.RELATIVE_FREQUENCY) { + return new Double(bin.getCount() / total); + } else if (this.type == HistogramType.SCALE_AREA_TO_1) { + return new Double(bin.getCount() / (binWidth * total)); + } else { // pretty sure this shouldn't ever happen + throw new IllegalStateException(); + } + } + + /** + * Returns the start value for a bin. + * + * @param series the series index (in the range 0 to + * getSeriesCount() - 1). + * @param item the item index (zero based). + * + * @return The start value. + * + * @throws IndexOutOfBoundsException if series is outside the + * specified range. + */ + @Override + public Number getStartX(int series, int item) { + List bins = getBins(series); + Weighted_HistogramBin bin = (Weighted_HistogramBin) bins.get(item); + return new Double(bin.getStartBoundary()); + } + + /** + * Returns the end value for a bin. + * + * @param series the series index (in the range 0 to + * getSeriesCount() - 1). + * @param item the item index (zero based). + * + * @return The end value. + * + * @throws IndexOutOfBoundsException if series is outside the + * specified range. + */ + @Override + public Number getEndX(int series, int item) { + List bins = getBins(series); + Weighted_HistogramBin bin = (Weighted_HistogramBin) bins.get(item); + return new Double(bin.getEndBoundary()); + } + + /** + * Returns the start y-value for a bin (which is the same as the y-value, + * this method exists only to support the general form of the + * {@link IntervalXYDataset} interface). + * + * @param series the series index (in the range 0 to + * getSeriesCount() - 1). + * @param item the item index (zero based). + * + * @return The y-value. + * + * @throws IndexOutOfBoundsException if series is outside the + * specified range. + */ + @Override + public Number getStartY(int series, int item) { + return getY(series, item); + } + + /** + * Returns the end y-value for a bin (which is the same as the y-value, + * this method exists only to support the general form of the + * {@link IntervalXYDataset} interface). + * + * @param series the series index (in the range 0 to + * getSeriesCount() - 1). + * @param item the item index (zero based). + * + * @return The Y value. + * + * @throws IndexOutOfBoundsException if series is outside the + * specified range. + */ + @Override + public Number getEndY(int series, int item) { + return getY(series, item); + } + + /** + * Tests this dataset for equality with an arbitrary object. + * + * @param obj the object to test against (null permitted). + * + * @return A boolean. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Weighted_HistogramDataset)) { + return false; + } + Weighted_HistogramDataset that = (Weighted_HistogramDataset) obj; + if (!Objects.equals(this.type, that.type)) { + return false; + } + if (!Objects.equals(this.list, that.list)) { + return false; + } + return true; + } + + /** + * Returns a clone of the dataset. + * + * @return A clone of the dataset. + * + * @throws CloneNotSupportedException if the object cannot be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + Weighted_HistogramDataset clone = (Weighted_HistogramDataset) super.clone(); + int seriesCount = getSeriesCount(); + clone.list = new java.util.ArrayList(seriesCount); + for (int i = 0; i < seriesCount; i++) { + clone.list.add(new LinkedHashMap((Map) this.list.get(i))); + } + return clone; + } + +} diff --git a/src/main/java/microsim/gui/plot/Weighted_HistogramSimulationPlotter.java b/src/main/java/microsim/gui/plot/Weighted_HistogramSimulationPlotter.java new file mode 100644 index 00000000..00ea4be8 --- /dev/null +++ b/src/main/java/microsim/gui/plot/Weighted_HistogramSimulationPlotter.java @@ -0,0 +1,452 @@ +package microsim.gui.plot; + +//package microsim.gui.plot; + +import java.awt.Color; +import java.util.ArrayList; + +import javax.swing.JInternalFrame; + +import microsim.engine.SimulationEngine; +import microsim.event.CommonEventType; +import microsim.event.EventListener; +import microsim.statistics.IUpdatableSource; +import microsim.statistics.weighted.IWeightedDoubleArraySource; +import microsim.statistics.weighted.IWeightedFloatArraySource; +import microsim.statistics.weighted.IWeightedIntArraySource; +import microsim.statistics.weighted.IWeightedLongArraySource; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.StandardXYBarPainter; +import org.jfree.chart.renderer.xy.XYBarRenderer; +import org.jfree.data.general.SeriesChangeEvent; +import org.jfree.data.statistics.HistogramType; + +/** + * A Weighted_HistogramSimulationPlotter is able to display a histogram of one + * or more + * data sources that each implements the Weight interface, + * and can be updated during the simulation. + * It is based on JFreeChart library and uses data sources based on the + * microsim.statistics.weighted* interfaces.
+ * Note that the weights are taken into account by adding the weight to the + * count + * of the histogram bin corresponding to the value associated with the weight. + * E.g, if a weighted object has value of 1.6 and weight of 5.3, the count of + * 5.3 + * is placed in the histogram bin appropriate for the value of 1.6. + * + * + *

+ * Title: JAS-mine + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2017 Ross Richardson + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Ross Richardson + *

+ */ +public class Weighted_HistogramSimulationPlotter extends JInternalFrame implements EventListener { + + private static final long serialVersionUID = 1L; + + final JFreeChart chart; + + private ArrayList sources; + + private Weighted_HistogramDataset dataset; + + private HistogramType type; + + private int bins; + + private Double minimum; + + private Double maximum; + + /** + * Constructor for histogram chart objects with chart legend displayed by + * default and + * all data samples shown, showing only the latest population data as time moves + * forward. + * Note - values falling on the boundary of adjacent bins will be assigned to + * the higher + * indexed bin. If it is desired set the minimum and maximum values displayed, + * or to turn + * the legend off, use the constructor: + * HistogramSimulationPlotter(String title, String xaxis, HistogramType type, + * int bins, double minimum, double maximum, boolean includeLegend) + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param type - the type of the histogram: either FREQUENCY, + * RELATIVE_FREQUENCY, or SCALE_AREA_TO_1 + * @param bins - the number of bins in the histogram + * + */ + public Weighted_HistogramSimulationPlotter(String title, String xaxis, HistogramType type, int bins) { // Includes + // legend by + // default + // and will + // accumulate + // data + // samples by + // default + // (if + // wanting + // only the + // most + // recent + // data + // points, + // use the + // other + // constructor) + this(title, xaxis, type, bins, null, null, true); + } + + /** + * Constructor for scatterplot chart objects, featuring a toggle to hide the + * chart legend + * and to set the minimum and maximum values displayed in the chart, with values + * below the minimum + * assigned to the first bin, and values above the maximum assigned to the last + * bin. Note - + * values falling on the boundary of adjacent bins will be assigned to the + * higher indexed bin. + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param type - the type of the histogram: either FREQUENCY, + * RELATIVE_FREQUENCY, or SCALE_AREA_TO_1 + * @param bins - the number of bins in the histogram + * @param minimum - any data value less than minimum will be assigned to + * the first bin + * @param maximum - any data value greater than maximum will be assigned + * to the last bin + * @param includeLegend - toggles whether to include the legend. If displaying a + * very large number of different series in the chart, it + * may be useful to turn + * the legend off as it will occupy a lot of space in the + * GUI. + */ + public Weighted_HistogramSimulationPlotter(String title, String xaxis, HistogramType type, int bins, Double minimum, + Double maximum, boolean includeLegend) { // Can specify whether to include legend and how many samples + // (updates) to display + // super(title, xaxis, type, bins, minimum, maximum, includeLegend); //invoke + // HistogramSimulationPlotter constructor + this.setResizable(true); + this.setTitle(title); + this.type = type; + this.bins = bins; + this.minimum = minimum; + this.maximum = maximum; + + sources = new ArrayList(); + + dataset = new Weighted_HistogramDataset(); + + String yaxis; + if (type.equals(HistogramType.FREQUENCY)) { + yaxis = "Frequency"; + } else if (type.equals(HistogramType.RELATIVE_FREQUENCY)) { + yaxis = "Relative Frequency"; + throw new IllegalArgumentException( + "ERROR - RELATIVE_FREQUENCY Histogram Type is not currently available for Weighted_HistogramSimulationPlotter! Please use FREQUENCY (or possibly SCALE_AREA_TO_1) as the Histogram Type instead."); + } else if (type.equals(HistogramType.SCALE_AREA_TO_1)) { + System.out.println( + "WARNING - the SCALE_AREA_TO_1 Weighted_HistogramSimulationPlotter has not been tested and may produce incorrect output!"); + yaxis = "Density (area scaled to 1)"; + } else + throw new IllegalArgumentException( + "Incorrect HistogramType argument when calling HistogramSimulationPlotter constructor!"); + + chart = ChartFactory.createHistogram( + title, // chart title + xaxis, // x axis label + yaxis, // y axis label + // type.toString(), //y axis label based on the type of the histogram + dataset, // data + PlotOrientation.VERTICAL, + includeLegend, // include legend + true, // tooltips + false // urls + ); + + // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... + chart.setBackgroundPaint(Color.white); + + // get a reference to the plot for further customisation... + final XYPlot plot = chart.getXYPlot(); + // plot.setBackgroundPaint(Color.lightGray); + plot.setBackgroundPaint(Color.white); + plot.setDomainGridlinePaint(Color.white); + plot.setRangeGridlinePaint(Color.white); + plot.setForegroundAlpha(0.85f); + + final XYBarRenderer renderer = new XYBarRenderer(); + renderer.setDrawBarOutline(false); + renderer.setBarPainter(new StandardXYBarPainter()); + renderer.setShadowVisible(false); + plot.setRenderer(renderer); + + final NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis(); + domainAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); + final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); + rangeAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits()); + + final ChartPanel chartPanel = new ChartPanel(chart); + + chartPanel.setPreferredSize(new java.awt.Dimension(500, 270)); + + setContentPane(chartPanel); + + this.setSize(400, 400); + } + + public void onEvent(Enum type) { + if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) { + update(); + } + } + + public void update() { + + dataset = new Weighted_HistogramDataset(); + dataset.setType(type); + chart.getXYPlot().setDataset(dataset); + + // int s = 0; + // Color color = (Color) chart.getXYPlot().getRenderer().getItemPaint(s, 0); + // int r = color.getRed(); + // int g = color.getGreen(); + // int b = color.getBlue(); + // chart.getXYPlot().getRenderer().setSeriesPaint(s, new Color(r, g, b, 130)); + + for (int i = 0; i < sources.size(); i++) { + WeightedArraySource cs = (WeightedArraySource) sources.get(i); + double[] vals = cs.getDoubleArray(); + double[] weights = cs.getWeights(); + if (minimum != null && maximum != null) { + dataset.addSeries(cs.label, vals, weights, bins, minimum, maximum); + } else + dataset.addSeries(cs.label, vals, weights, bins); + + } + dataset.seriesChanged( + new SeriesChangeEvent(new String("Update at time " + SimulationEngine.getInstance().getTime()))); + + } + + private abstract class WeightedArraySource { + public String label; + protected boolean isUpdatable; + + public abstract double[] getDoubleArray(); + + public abstract double[] getWeights(); + } + + private class DWeightedArraySource extends WeightedArraySource { + public IWeightedDoubleArraySource source; + + public DWeightedArraySource(String label, IWeightedDoubleArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getDoubleArray(); + } + + @Override + public double[] getWeights() { + return source.getWeights(); + } + } + + private class FWeightedArraySource extends WeightedArraySource { + public IWeightedFloatArraySource source; + + public FWeightedArraySource(String label, IWeightedFloatArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + float[] array = source.getFloatArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + + @Override + public double[] getWeights() { + return source.getWeights(); + } + } + + private class IWeightedArraySource extends WeightedArraySource { + public IWeightedIntArraySource source; + + public IWeightedArraySource(String label, IWeightedIntArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + int[] array = source.getIntArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + + @Override + public double[] getWeights() { + return source.getWeights(); + } + } + + private class LWeightedArraySource extends WeightedArraySource { + public IWeightedLongArraySource source; + + public LWeightedArraySource(String label, IWeightedLongArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + long[] array = source.getLongArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + + @Override + public double[] getWeights() { + return source.getWeights(); + } + } + + /** + * Add a new series buffer, retrieving value from IWeightedDoubleSource objects + * in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, IWeightedDoubleArraySource source) { + DWeightedArraySource sequence = new DWeightedArraySource(name, source); + sources.add(sequence); + } + + /** + * Add a new series buffer, retrieving value from IWeightedFloatSource objects + * in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, IWeightedFloatArraySource source) { + FWeightedArraySource sequence = new FWeightedArraySource(name, source); + sources.add(sequence); + } + + /** + * Add a new series buffer, retrieving value from IWeightedIntArraySource + * objects in a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, IWeightedIntArraySource source) { + IWeightedArraySource sequence = new IWeightedArraySource(name, source); + sources.add(sequence); + } + + /** + * Add a new series buffer, retrieving value from IWeightedLongSource objects in + * a + * collection. + * + * @param name + * The name of the series, which is shown in the legend. + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(String name, IWeightedLongArraySource source) { + LWeightedArraySource sequence = new LWeightedArraySource(name, source); + sources.add(sequence); + } + +} diff --git a/src/main/java/microsim/gui/plot/Weighted_PyramidDataset.java b/src/main/java/microsim/gui/plot/Weighted_PyramidDataset.java new file mode 100644 index 00000000..840b6b59 --- /dev/null +++ b/src/main/java/microsim/gui/plot/Weighted_PyramidDataset.java @@ -0,0 +1,334 @@ +package microsim.gui.plot; + +/* (C) Copyright 2020, by Kostas Manios + * Based on Ross Richardson's Weighted_HistogramDataset.java, which in turn + * was based on JFreeChart's HistogramDataset.java by Object Refinery Limited + * and Contributors. + * + * Project Info: http://www.jfree.org/jfreechart/index.html + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * --------------------- + * Weighted_PyramidDataset.java + * --------------------- + * (C) Copyright 2020, by Kostas Manios + * + * (C) Copyright 2017, by Ross Richardson + * + * Based on JFreeChart's HistogramDataset.java: + * (C) Copyright 2003-2013, by Jelai Wang and Contributors. + * + * Original Author: Jelai Wang (jelaiw AT mindspring.com); + * Contributor(s): David Gilbert (for Object Refinery Limited); + * Cameron Hayne; + * Rikard Bj?rklind; + * Thomas A Caswell (patch 2902842); + * + * + */ + +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jfree.data.general.AbstractSeriesDataset; +import org.jfree.data.general.DatasetChangeListener; +import org.jfree.data.general.DatasetGroup; +import org.jfree.chart.util.Args; +import org.jfree.chart.util.PublicCloneable; +import org.jfree.data.category.CategoryDataset; + +import microsim.gui.plot.Weighted_PyramidPlotter.GroupName; + +/** + * A weighted dataset that can be used for creating weighted pyramids. + */ +public class Weighted_PyramidDataset extends AbstractSeriesDataset + implements CategoryDataset, Cloneable, PublicCloneable, + Serializable { + /** For serialization. */ + private static final long serialVersionUID = -6875925093485823495L; + + /** A list of maps. */ + private Map> dataMap; + private double[][] groupRanges; + private GroupName[] groupNames; + private double scalingFactor = 1.0; + + /** + * Creates a new dataset using the provided groupNames and + * groupRanges to build a HashMap of total group weight. + * The weights are adjusted by the provided scalingFactor. + * + * @param groupNames the names of each group to be generated + * (null not permitted). + * @param groupRanges the ranges of each group to be generated + * (null not permitted). + * @param scalingFactor the scaling factor for the weights (null + * not permitted). + */ + public Weighted_PyramidDataset(GroupName[] groupNames, double[][] groupRanges, double scalingFactor) { + Args.nullNotPermitted(groupNames, "groupNames"); + Args.nullNotPermitted(groupRanges, "groupRanges"); + Args.nullNotPermitted(scalingFactor, "scalingFactor"); + this.dataMap = new HashMap>(); + this.groupNames = groupNames; + this.groupRanges = groupRanges; + this.scalingFactor = scalingFactor; + } + + /** + * Adds the couple of series to the dataMap. Each value is assigned + * to a group when it matches the group's min/max limits. + * + * @param keys the series key (null not permitted). + * @param values the raw observations. (null not permitted). + * @param weightings the weights associated with the values, i.e. + * weight i indicates the number of times the value i appears + * (null not permitted). + */ + public void addSeries(String[] keys, double[][] values, double[][] weightings) { + Args.nullNotPermitted(keys, "key"); + Args.nullNotPermitted(values, "values"); + Args.nullNotPermitted(weightings, "weightings"); + if (values.length != 2 || weightings.length != 2) { + throw new IllegalArgumentException( + "You must provide a pair of series!"); + } + if (values[0].length != weightings[0].length || values[1].length != weightings[1].length) { + throw new IllegalArgumentException( + "The length of weightings array must be the same as the values array for each series!"); + } + + // Create and add the two series to the dataMap + for (int s = 0; s < 2; s++) { + // for each series create a new bucket to store the variable sums + Map bucket = new HashMap(); + + for (int v = 0; v < values[s].length; v++) { // for each value + for (int g = 0; g < this.groupNames.length; g++) { // for each group + // if the value matches the group, add to the correct bucket element + if (values[s][v] >= this.groupRanges[g][0] && values[s][v] <= this.groupRanges[g][1]) { + // if the element does not exist, create it + if (!bucket.containsKey(this.groupNames[g])) + bucket.put(this.groupNames[g], 0.); + // multiply the weight by the scaling factor and add to the existing sum (negate + // if this is the left side), + bucket.put(this.groupNames[g], bucket.get(this.groupNames[g]) + + weightings[s][v] * (s == 1 ? scalingFactor : -scalingFactor)); + // do not check any more groups for this value + break; + } + } + } + // store the series bucket + dataMap.put(keys[s], bucket); + } + } + + /** + * Returns the minimum value in an array of values. + * + * @param values the values (null not permitted and + * zero-length array not permitted). + * + * @return The minimum value. + */ + private double getMinimum(double[] values) { + if (values == null || values.length < 1) { + throw new IllegalArgumentException( + "Null or zero length 'values' argument."); + } + double min = Double.MAX_VALUE; + for (int i = 0; i < values.length; i++) { + if (values[i] < min) { + min = values[i]; + } + } + return min; + } + + /** + * Returns the maximum value in an array of values. + * + * @param values the values (null not permitted and + * zero-length array not permitted). + * + * @return The maximum value. + */ + private double getMaximum(double[] values) { + if (values == null || values.length < 1) { + throw new IllegalArgumentException( + "Null or zero length 'values' argument."); + } + double max = -Double.MAX_VALUE; + for (int i = 0; i < values.length; i++) { + if (values[i] > max) { + max = values[i]; + } + } + return max; + } + + /** + * Tests this dataset for equality with an arbitrary object. + * + * @param obj the object to test against (null permitted). + * + * @return A boolean. + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Weighted_PyramidDataset)) { + return false; + } + return true; + } + + /** + * Returns a clone of the dataset. + * + * @return A clone of the dataset. + * + * @throws CloneNotSupportedException if the object cannot be cloned. + */ + @Override + public Object clone() throws CloneNotSupportedException { + Weighted_PyramidDataset clone = (Weighted_PyramidDataset) super.clone(); + return clone; + } + + public double[][] getDataArray() { + double[][] data = new double[dataMap.keySet().size()][groupNames.length]; + int i = 0; + for (Map v : dataMap.values()) { + int j = 0; + for (GroupName entry : groupNames) + // make sure that if either side of the dataset is missing a value, this is + // filled with 0 + data[i][j++] = v.containsKey(entry) ? v.get(entry) : 0; + i++; + } + + return data; + } + + @Override + public List getColumnKeys() { + // TODO Auto-generated method stub + return Arrays.asList(this.groupNames); + } + + @Override + public Comparable getColumnKey(int column) { + return this.groupNames[column]; + } + + public String[] getSeriesKeys() { + return dataMap.keySet().toArray(new String[] {}); + } + + @Override + public Comparable getRowKey(int row) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getRowIndex(Comparable key) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public List getRowKeys() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getColumnIndex(Comparable key) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Number getValue(Comparable rowKey, Comparable columnKey) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getRowCount() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getColumnCount() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Number getValue(int row, int column) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void addChangeListener(DatasetChangeListener listener) { + // TODO Auto-generated method stub + + } + + @Override + public void removeChangeListener(DatasetChangeListener listener) { + // TODO Auto-generated method stub + + } + + @Override + public DatasetGroup getGroup() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setGroup(DatasetGroup group) { + // TODO Auto-generated method stub + + } + + @Override + public int getSeriesCount() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Comparable getSeriesKey(int series) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/microsim/gui/plot/Weighted_PyramidPlotter.java b/src/main/java/microsim/gui/plot/Weighted_PyramidPlotter.java new file mode 100644 index 00000000..83aebea5 --- /dev/null +++ b/src/main/java/microsim/gui/plot/Weighted_PyramidPlotter.java @@ -0,0 +1,702 @@ +package microsim.gui.plot; + +import java.awt.Color; +import java.text.DecimalFormat; +import java.util.Arrays; + +import javax.swing.JInternalFrame; + +import microsim.event.CommonEventType; +import microsim.event.EventListener; +import microsim.statistics.IUpdatableSource; +import microsim.statistics.weighted.IWeightedDoubleArraySource; +import microsim.statistics.weighted.IWeightedFloatArraySource; +import microsim.statistics.weighted.IWeightedIntArraySource; +import microsim.statistics.weighted.IWeightedLongArraySource; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.CategoryPlot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.renderer.category.StackedBarRenderer; +import org.jfree.chart.renderer.category.StandardBarPainter; +import org.jfree.data.general.DatasetUtils; + +/** + * A PyramidPlotter is able to display a pyramid using two weighted + * cross-sections of a variable (e.g. dag males/females for a + * population pyramid). It can be updated during the simulation. It + * is based on JFreeChart library and uses data sources based on the + * microsim.statistics.weighted* interfaces.
+ * Note that the weights are taken into account by adding the weight to the + * count + * of each group. Groups can be optionally provided by the caller. + * + * + *

+ * Title: JAS-mine + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2020 Kostas Manios + *

+ * + * This work is based on "Weighted_HistogramSimulationPlotter.java" by Ross + * Richardson + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Kostas Manios + * + *

+ */ +public class Weighted_PyramidPlotter extends JInternalFrame implements EventListener { + + /** + * Default values + */ + public static String DEFAULT_TITLE = "Population Chart"; + public static String DEFAULT_XAXIS = "Age Group"; + public static String DEFAULT_YAXIS = "Population"; + public static String DEFAULT_LEFT_CAT = "Males"; + public static String DEFAULT_RIGHT_CAT = "Females"; + public static Boolean DEFAULT_REVERSE_ORDER = false; + public static String DEFAULT_YAXIS_FORMAT = "#.##"; + private static int MAXIMUM_VISIBLE_CATEGORIES = 20; + + /** + * Variables + */ + + private static final long serialVersionUID = 1L; + + private JFreeChart chart; + + private WeightedArraySource[] sources; + + private Weighted_PyramidDataset dataset; + + private String xaxis; + + private String yaxis; + + private String yaxisFormat = DEFAULT_YAXIS_FORMAT; + + private final String[] catNames = new String[2]; + + private GroupName[] groupNames; + + private double[][] groupRanges; // These need to be doubles for the DatasetUtilities.createCategoryDataset + // method + + private double scalingFactor; // This scales the sample (e.g. to the whole population) + + /** + * Constructor for pyramid objects, showing only the latest data as time moves + * forward. + * Default values are used for all parameters: title, x-axis, y-axis, category + * names, age group names/ranges, reverseOrder + * It generates one age group per unique age, whose title is that age. + * + */ + public Weighted_PyramidPlotter() { + this(DEFAULT_TITLE, DEFAULT_XAXIS, DEFAULT_YAXIS, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT); + } + + /** + * Constructor for pyramid objects, showing only the latest data as time moves + * forward. + * Default values are used for the following parameters: x-axis, y-axis, + * category names, age group names/ranges, reverseOrder + * It generates one age group per unique age, whose title is that age. + * + * @param title - title of the chart + * + */ + public Weighted_PyramidPlotter(String title) { + this(title, DEFAULT_XAXIS, DEFAULT_YAXIS, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT); + } + + /** + * Constructor for pyramid objects, showing only the latest data as time moves + * forward. + * Default values are used for the following parameters: category names, age + * group names/ranges, reverseOrder + * It generates one age group per unique age, whose title is that age. + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param yaxis - name of the y-axis + * + * + * public PopulationPyramidPlotter(String title, String xaxis, + * String yaxis) { + * this(title, xaxis, yaxis, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT); + * } + */ + + /** + * Constructor for pyramid objects, showing only the latest data as time moves + * forward. + * Default values are used for the following parameters: age group names/ranges, + * reverseOrder + * It generates one age group per unique age, whose title is that age. + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param yaxis - name of the y-axis + * @param leftCat - the name of the left category + * @param rightCat - the name of the right category + * + */ + public Weighted_PyramidPlotter(String title, String xaxis, String yaxis, String leftCat, String rightCat) { + // fix the titles and prepare the plotter, leaving the groups null + fixTitles(title, xaxis, yaxis, leftCat, rightCat); + preparePlotter(); + } + + /** + * Constructor for pyramid objects, showing only the latest data as time moves + * forward. + * It generates groups names and ranges using the start/end/step values + * provided. + * Default values are used for the following parameters: x-axis, y-axis, + * category names, age group names/ranges, reverseOrder + * + * @param start - the minimum accepted value in groups + * @param end - the maximum accepted value in groups + * @param step - the step used to separate value into groups + * + */ + public Weighted_PyramidPlotter(int start, int end, int step) { + this(DEFAULT_TITLE, DEFAULT_XAXIS, DEFAULT_YAXIS, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT, start, end, step, + DEFAULT_REVERSE_ORDER, DEFAULT_YAXIS_FORMAT); + } + + /** + * Constructor for pyramid objects, showing only the latest data as time moves + * forward. + * It generates groups names and ranges using the start/end/step and order + * values provided. + * Default values are used for the following parameters: x-axis, y-axis, + * category names, age group names/ranges + * + * @param start - the minimum accepted value in groups + * @param end - the maximum accepted value in groups + * @param step - the step used to separate value into groups + * @param reverseOrder - if true, it will reverse the groups + * + */ + public Weighted_PyramidPlotter(int start, int end, int step, Boolean reverseOrder) { + this(DEFAULT_TITLE, DEFAULT_XAXIS, DEFAULT_YAXIS, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT, start, end, step, + reverseOrder, DEFAULT_YAXIS_FORMAT); + } + + /** + * Constructor for pyramid objects, showing only the latest data as time moves + * forward. + * It generates groups names and ranges using the start/end/step values + * provided. + * Descending order is used by default. + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param yaxis - name of the y-axis + * @param leftCat - the name of the left category + * @param rightCat - the name of the right category + * @param start - the minimum accepted value in groups + * @param end - the maximum accepted value in groups + * @param step - the step used to separate value into groups + * @param reverseOrder - if true, it will reverse the groups + * + */ + public Weighted_PyramidPlotter(String title, String xaxis, String yaxis, String leftCat, String rightCat, int start, + int end, int step, Boolean reverseOrder, String format) { + if (step == 0) + return; + fixTitles(title, xaxis, yaxis, leftCat, rightCat); + yaxisFormat = format; + + // Create the groups based on the range, and save them to "this" + GroupDetails gd = makeGroupsFromRange(start, end, step, reverseOrder, format); + this.groupNames = gd.groupNames; + this.groupRanges = gd.groupRanges; + + preparePlotter(); + } + + /** + * Constructor for pyramid objects, showing only the latest data as time moves + * forward. + * It generates groups based on the names and ranges provided. + * Default values are used for the following parameters: title, x-axis, y-axis, + * category names + * + * @param groupNames - an array of the name of each group + * @param groupRanges - an array of the min/max values of each group + * + */ + public Weighted_PyramidPlotter(String[] groupNames, double[][] groupRanges) { + this(DEFAULT_TITLE, DEFAULT_XAXIS, DEFAULT_YAXIS, DEFAULT_LEFT_CAT, DEFAULT_RIGHT_CAT, groupNames, groupRanges, + DEFAULT_YAXIS_FORMAT); + } + + /** + * Constructor for pyramid objects, showing only the latest data as time moves + * forward. + * It generates groups based on the names and ranges provided. + * + * @param title - title of the chart + * @param xaxis - name of the x-axis + * @param yaxis - name of the y-axis + * @param leftCat - the name of the left category + * @param rightCat - the name of the right category + * @param groupNames - an array of the name of each group + * @param groupRanges - an array of the min/max values of each group + * + */ + public Weighted_PyramidPlotter(String title, String xaxis, String yaxis, String leftCat, String rightCat, + String[] groupNames, double[][] groupRanges, String format) { + fixTitles(title, xaxis, yaxis, leftCat, rightCat); + yaxisFormat = format; + + // Fix names + this.groupNames = groupNames == null ? null : getGroupNamesFromStrings(groupNames); + this.groupRanges = groupRanges; + + preparePlotter(); + } + + // The function that prepares the titles + private void fixTitles(String title, String xaxis, String yaxis, String leftCat, String rightCat) { + this.setTitle(title); + this.xaxis = xaxis; + this.yaxis = yaxis; + this.catNames[0] = leftCat; + this.catNames[1] = rightCat; + } + + // the function that calculates groups from a range + private GroupDetails makeGroupsFromRange(int start, int end, int step, Boolean reverseOrder, String format) { + // First we calculate the optimal (visually at least!) number of groups, so that + // the last group ends with "max" and its size is "(0.5 * step) < size < + // (1.5*step)" + int noOfGroups = (int) Math + .max(Math.round((double) (end - start) / (double) step) + (Math.abs(step) == 1 ? 1 : 0), 1); + // *Note: should we enforce equal groups sizes? + + // Then, if required, we reverse the order + if (reverseOrder) { + int temp = start; + start = end; + end = temp; + step = -step; + } + + // Then we calculate the group ranges & names + String[] groupNames = new String[noOfGroups]; + double[][] groupRanges = new double[noOfGroups][2]; + + // asc checks whether we are ascending or descending + Boolean asc = start <= end; + for (int i = 0; i < noOfGroups; i++) { + // The range needs to always be stored in ascending order, hence the extended + // use of "asc" here. Sorry! :) + // is calculated based on the current step value + groupRanges[i][asc ? 0 : 1] = start + i * step; + // is equal to the next group's " - 1", but for the last group it is + // equal to "end" + groupRanges[i][asc ? 1 : 0] = (i == noOfGroups - 1) ? end : (start + (i + 1) * step) - (asc ? 1 : -1); + // for the name, if step=1 use the step value, else show as "from - to" + // (inclusive) + groupNames[i] = groupRanges[i][0] == groupRanges[i][1] ? new DecimalFormat(format).format(groupRanges[i][0]) + : new DecimalFormat(format).format(groupRanges[i][asc ? 0 : 1]) + " - " + + new DecimalFormat(format).format(groupRanges[i][asc ? 1 : 0]); + + } + + return new GroupDetails(getGroupNamesFromStrings(groupNames), groupRanges); + } + + private static GroupName[] getGroupNamesFromStrings(String[] groupStrings) { + GroupName[] groupNames = new GroupName[groupStrings.length]; + + int stepShow = (int) Math.ceil((double) groupStrings.length / (double) MAXIMUM_VISIBLE_CATEGORIES); + + // Show only every Nth string and always the first & last + for (int i = 0; i < groupStrings.length; i++) { + groupNames[i] = new GroupName(groupStrings[i], (i % stepShow == 0 || i == groupStrings.length - 1)); + } + + return groupNames; + } + + private void preparePlotter() { + this.setResizable(true); + sources = new WeightedArraySource[2]; + + chart = ChartFactory.createStackedBarChart( + title, // chart title + this.xaxis, // x axis label + this.yaxis, // y axis label + DatasetUtils.createCategoryDataset(this.catNames, new String[] { "" }, + new double[][] { { 0 }, { 0 } }), + PlotOrientation.HORIZONTAL, + true, // include legend + true, + true); + + setChartProperties(); + + chart.getCategoryPlot().getRangeAxis().setVisible(false); + + final ChartPanel chartPanel = new ChartPanel(chart); + + chartPanel.setPreferredSize(new java.awt.Dimension(500, 270)); + + setContentPane(chartPanel); + + this.setSize(400, 400); + } + + public void onEvent(Enum type) { + if (type instanceof CommonEventType && type.equals(CommonEventType.Update)) { + update(); + } + } + + // This function generates a new chart based on the latest data + public void update() { + if (sources.length != 2 || catNames.length != 2) + return; + GroupName[] groupNames = null; + double[][] groupRanges = null; + + // Get the source data + WeightedArraySource leftData = (WeightedArraySource) sources[0]; + WeightedArraySource rightData = (WeightedArraySource) sources[1]; + final double[][] vals = new double[][] { leftData.getDoubleArray(), rightData.getDoubleArray() }; + final double[][] weights = new double[][] { leftData.getWeights(), rightData.getWeights() }; + + // If there are no groups defined, create one for each age between the min/max + // found in the data + // *Note: do we want this done in every repetition, or should we save to "this"? + if (this.groupNames == null || this.groupRanges == null) { + int min = (int) Math.min(Arrays.stream(vals[0]).min().orElse(0), Arrays.stream(vals[1]).min().orElse(0)); // if + // there + // is + // no + // data, + // set + // min + // to + // 0 + int max = (int) Math.min(Arrays.stream(vals[0]).max().orElse(100), + Arrays.stream(vals[1]).max().orElse(100)); // if there is no data, set max to 100 + // Create the groups based on the range, and save them to the local variables + GroupDetails gd = makeGroupsFromRange(min, max, 1, true, yaxisFormat); + groupNames = gd.groupNames; + groupRanges = gd.groupRanges; + } else { + // else, just use the existing groups + groupNames = this.groupNames; + groupRanges = this.groupRanges; + } + + // Create the dataset and add the data + dataset = new Weighted_PyramidDataset(groupNames, groupRanges, scalingFactor); + dataset.addSeries(this.catNames, vals, weights); + + chart = ChartFactory.createStackedBarChart( + this.title, // chart title + this.xaxis, // x axis label + this.yaxis, // y axis label + DatasetUtils.createCategoryDataset( + dataset.getSeriesKeys(), + groupNames, + dataset.getDataArray()), // data + PlotOrientation.HORIZONTAL, + true, // include legend + true, + true); + + setChartProperties(); + + final ChartPanel chartPanel = new ChartPanel(chart); + + chartPanel.setPreferredSize(new java.awt.Dimension(500, 270)); + + setContentPane(chartPanel); + + } + + /** + * This function sets the default Chart Properties. + */ + private void setChartProperties() { + // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... + chart.setBackgroundPaint(Color.white); + + // get a reference to the plot for further customisation... + final CategoryPlot plot = chart.getCategoryPlot(); + plot.setBackgroundPaint(Color.lightGray); + plot.setRangeGridlinePaint(Color.white); + plot.setForegroundAlpha(0.85f); + plot.setShadowGenerator(null); + final StackedBarRenderer renderer = new StackedBarRenderer(); + renderer.setDrawBarOutline(false); + renderer.setBarPainter(new StandardBarPainter()); + renderer.setShadowVisible(false); + plot.setRenderer(renderer); + + // hide the sign for negative numbers in yAxis + NumberAxis yAxis = (NumberAxis) plot.getRangeAxis(); + yAxis.setNumberFormatOverride(new DecimalFormat("0; 0 ")); + + } + + private class GroupDetails { + public GroupName[] groupNames; + public double[][] groupRanges; + + public GroupDetails(GroupName[] groupNames, double[][] groupRanges) { + this.groupNames = groupNames; + this.groupRanges = groupRanges; + } + } + + public void setScalingFactor(double scalingFactor) { + this.scalingFactor = scalingFactor; + } + + public static class GroupName implements Comparable { + String value; + Boolean show; + + GroupName(String val, Boolean sh) { + value = val; + show = sh; + } + + public int compareTo(GroupName key) { + return value.compareTo(key.value); + } + + public String toString() { + return show ? value : ""; + } + } + + private abstract class WeightedArraySource { + public String label; + protected boolean isUpdatable; + + public abstract double[] getDoubleArray(); + + public abstract double[] getWeights(); + } + + private class DWeightedArraySource extends WeightedArraySource { + public IWeightedDoubleArraySource source; + + public DWeightedArraySource(String label, IWeightedDoubleArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + return source.getDoubleArray(); + } + + @Override + public double[] getWeights() { + return source.getWeights(); + } + } + + private class FWeightedArraySource extends WeightedArraySource { + public IWeightedFloatArraySource source; + + public FWeightedArraySource(String label, IWeightedFloatArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + float[] array = source.getFloatArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + + @Override + public double[] getWeights() { + return source.getWeights(); + } + } + + private class IWeightedArraySource extends WeightedArraySource { + public IWeightedIntArraySource source; + + public IWeightedArraySource(String label, IWeightedIntArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + int[] array = source.getIntArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + + @Override + public double[] getWeights() { + return source.getWeights(); + } + } + + private class LWeightedArraySource extends WeightedArraySource { + public IWeightedLongArraySource source; + + public LWeightedArraySource(String label, IWeightedLongArraySource source) { + super.label = label; + this.source = source; + isUpdatable = (source instanceof IUpdatableSource); + } + + /* + * (non-Javadoc) + * + * @see jas.plot.TimePlot.Source#getDouble() + */ + public double[] getDoubleArray() { + if (isUpdatable) + ((IUpdatableSource) source).updateSource(); + long[] array = source.getLongArray(); + double[] output = new double[array.length]; + for (int i = 0; i < array.length; i++) + output[i] = array[i]; + + return output; + } + + @Override + public double[] getWeights() { + return source.getWeights(); + } + } + + /** + * Add a new series buffer, retrieving value from IWeightedDoubleSource objects + * in a + * collection. + * + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(IWeightedDoubleArraySource[] source) { + if (source.length != 2) + return; + if (catNames.length != 2) + return; + sources[0] = new DWeightedArraySource(catNames[0], source[0]); + sources[1] = new DWeightedArraySource(catNames[1], source[1]); + } + + /** + * Add a new series buffer, retrieving value from IWeightedFloatSource objects + * in a + * collection. + * + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(IWeightedFloatArraySource[] source) { + if (source.length != 2) + return; + if (catNames.length != 2) + return; + sources[0] = new FWeightedArraySource(catNames[0], source[0]); + sources[1] = new FWeightedArraySource(catNames[1], source[1]); + } + + /** + * Add a new series buffer, retrieving value from IWeightedIntArraySource + * objects in a + * collection. + * + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(IWeightedIntArraySource[] source) { + if (source.length != 2) + return; + if (catNames.length != 2) + return; + sources[0] = new IWeightedArraySource(catNames[0], source[0]); + sources[1] = new IWeightedArraySource(catNames[1], source[1]); + } + + /** + * Add a new series buffer, retrieving value from IWeightedLongSource objects in + * a + * collection. + * + * @param source + * A collection containing the sources. + */ + public void addCollectionSource(IWeightedLongArraySource[] source) { + if (source.length != 2) + return; + if (catNames.length != 2) + return; + sources[0] = new LWeightedArraySource(catNames[0], source[0]); + sources[1] = new LWeightedArraySource(catNames[1], source[1]); + } + +} diff --git a/src/main/java/microsim/gui/probe/IProbeFields.java b/src/main/java/microsim/gui/probe/IProbeFields.java new file mode 100644 index 00000000..674c0ef9 --- /dev/null +++ b/src/main/java/microsim/gui/probe/IProbeFields.java @@ -0,0 +1,49 @@ +package microsim.gui.probe; + +import java.util.List; + +/** + * This interface allows the object implementing it to + * show only some properties and methods when probed.
+ * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ + +public interface IProbeFields { + /** + * Return a list contaning strings corresponding to + * the properties and the methods shown by a probe. + * + * @return A list of String objects. + */ + public List getProbeFields(); +} diff --git a/src/main/java/microsim/gui/probe/MethodDialog.java b/src/main/java/microsim/gui/probe/MethodDialog.java new file mode 100644 index 00000000..7fe9be2a --- /dev/null +++ b/src/main/java/microsim/gui/probe/MethodDialog.java @@ -0,0 +1,117 @@ +package microsim.gui.probe; + +import java.awt.*; +import javax.swing.*; +import java.awt.event.*; + +import java.lang.reflect.*; + +/** + * Not of interest for users. + * A dialog used by the probe to get input parameters + * from user. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ + +public class MethodDialog extends JDialog { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + JPanel jPanelMain = new JPanel(); + JScrollPane jScrollPaneTable = new JScrollPane(); + MethodParameterDataModel parameters;// = new MethodParameterDataModel(null); + JTable jTableMethods = new JTable(); + JButton jBtnCancel = new JButton(); + JButton jBtnExecute = new JButton(); + + public boolean cancel; + + public MethodDialog(Frame frame, String title, boolean modal, Method m) { + super(frame, title, modal); + try { + parameters = new MethodParameterDataModel(m); + jTableMethods.setModel(parameters); + + for (int i = 0; i < jTableMethods.getColumnModel().getColumnCount(); i++) + jTableMethods.getColumnModel().getColumn(i).setHeaderValue( + parameters.getHeaderText(i)); + + jbInit(); + setSize(200, 200); + setLocation(200, 200); + setTitle("Enter parameters for method " + m.toString()); + pack(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public MethodDialog(Method m) { + this(null, "", false, m); + } + + void jbInit() throws Exception { + jBtnExecute.setText("Execute"); + jBtnExecute.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnExecute_actionPerformed(e); + } + }); + jBtnCancel.setText("Cancel"); + jBtnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnCancel_actionPerformed(e); + } + }); + this.getContentPane().add(jPanelMain, BorderLayout.SOUTH); + jPanelMain.add(jBtnCancel, null); + jPanelMain.add(jBtnExecute, null); + this.getContentPane().add(jScrollPaneTable, BorderLayout.CENTER); + jScrollPaneTable.getViewport().add(jTableMethods, null); + } + + void jBtnCancel_actionPerformed(ActionEvent e) { + cancel = true; + dispose(); + } + + void jBtnExecute_actionPerformed(ActionEvent e) { + cancel = false; + dispose(); + } + + public Object[] getParameters() { + return parameters.getParams(); + } +} diff --git a/src/main/java/microsim/gui/probe/MethodParameterDataModel.java b/src/main/java/microsim/gui/probe/MethodParameterDataModel.java new file mode 100644 index 00000000..7c6deb51 --- /dev/null +++ b/src/main/java/microsim/gui/probe/MethodParameterDataModel.java @@ -0,0 +1,201 @@ +package microsim.gui.probe; + +import javax.swing.*; +import javax.swing.table.*; + +import java.lang.reflect.*; + +/** + * Not of interest for users. + * A data model used to collect input parameters of a method. + * It is used by the table of the MethodDialog frame. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class MethodParameterDataModel extends AbstractTableModel { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + private static final int COLUMNS = 3; + + private static final int COL_FIELD = 0; + private static final int COL_VALUE = 2; + private static final int COL_TYPE = 1; + + Method targetObj; + Object[][] data; + + public MethodParameterDataModel(Method objToInspect) { + targetObj = objToInspect; + if (ProbeReflectionUtils.isAnExecutableMethod(targetObj)) + update(); + } + + public void update() { + Class[] params = targetObj.getParameterTypes(); + if (params == null) { + System.out.println("No args"); + return; + } + data = new Object[params.length][COLUMNS]; + + for (int i = 0; i < params.length; i++) { + // data[i][COL_VALUE] = new String(""); + data[i][COL_VALUE] = ""; // Modification by Ross (See J. Bloch "Effective Java" 2nd Edition, Item 5) + data[i][COL_TYPE] = params[i].getName(); + data[i][COL_FIELD] = params[i]; + } + } + + public String getHeaderText(int column) { + switch (column + 1) { + case COL_VALUE: + return "Value"; + case COL_TYPE: + return "Type"; + default: + return ""; + } + } + + public int getColumnCount() { + if (data == null) + return 0; + else + return COLUMNS - 1; + } + + public Object getValueAt(int row, int col) { + return data[row][col + 1]; + } + + public int getRowCount() { + if (data == null) + return 0; + else + return data.length; + } + + public void setValueAt(Object val, int row, int col) { + + try { + Object f = data[row][COL_FIELD]; + if (f == null) { + JOptionPane.showMessageDialog(null, "The argument is of unknown type.\n It is impossible to edit.", + "Probe editing variable", JOptionPane.INFORMATION_MESSAGE); + return; + } + + data[row][col + 1] = val; + + } catch (Exception e) { + System.out.println("Error setting field: " + e.getMessage()); + return; + } + // Indicate the change has happened: + fireTableDataChanged(); + } + + public Object[] getParams() { + Object[] obs = new Object[getRowCount()]; + Object o; + + try { + + for (int i = 0; i < obs.length; i++) { + o = getWrapper(data[i][COL_TYPE].toString(), data[i][COL_VALUE].toString()); + if (o == null) { + Class cc = Class.forName(data[i][COL_TYPE].toString()); + Constructor c = cc.getDeclaredConstructor(new Class[] { (new String()).getClass() }); + o = c.newInstance(new Object[] { data[i][COL_VALUE].toString() }); + } + obs[i] = o; + } + } catch (Exception e) { + System.out.println("Err in getParams: " + e.getMessage()); + } + + return obs; + + } + + private Object getWrapper(String s, String o) { + if (s.equals(Integer.TYPE.getName())) + return new Integer(Integer.parseInt(o)); + if (s.equals(Double.TYPE.getName())) + return new Double(Double.parseDouble(o)); + if (s.equals(Boolean.TYPE.getName())) + return new Boolean(Boolean.valueOf(o).booleanValue()); + if (s.equals(Byte.TYPE.getName())) + return new Byte(Byte.parseByte(o)); + if (s.equals(Character.TYPE.getName())) + return new Character(o.charAt(0)); + if (s.equals(Float.TYPE.getName())) + return new Float(Float.parseFloat(o)); + if (s.equals(Long.TYPE.getName())) + return new Long(Long.parseLong(o)); + if (s.equals(Short.TYPE.getName())) + return new Short(Short.parseShort(o)); + + return null; + } + + public Object getObjectAtRow(int row) { + try { + return data[row][COL_FIELD]; + } catch (Exception e) { + return null; + } + } + + public String getObjectNameAtRow(int row) { + try { + return ((Class) data[row][COL_FIELD]).getName(); + } catch (Exception e) { + return ""; + } + } + + public boolean isCellEditable(int row, int col) { + if ((col + 1) == COL_VALUE) + return true; + else + return false; + } + + public Object getProbedObject() { + return targetObj; + } +} diff --git a/src/main/java/microsim/gui/probe/MethodsDataModel.java b/src/main/java/microsim/gui/probe/MethodsDataModel.java new file mode 100644 index 00000000..f86daf5f --- /dev/null +++ b/src/main/java/microsim/gui/probe/MethodsDataModel.java @@ -0,0 +1,183 @@ +package microsim.gui.probe; + +import javax.swing.ListModel; +import javax.swing.event.ListDataListener; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.swing.JOptionPane; + +import java.util.List; +import java.util.ArrayList; + +import java.lang.reflect.*; + +/** + * Not of interest for users. + * A data model used to show the list of methods into the probe. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class MethodsDataModel implements ListModel { + + private static final Logger log = LogManager.getLogger(MethodsDataModel.class); + + private List methods; + private Object targetObj; + private boolean viewPrivate; + private List probeFields; + + private int deepLevel = 0; + + public MethodsDataModel(Object o) { + methods = new ArrayList(); + targetObj = o; + viewPrivate = true; + try { + probeFields = ((IProbeFields) o).getProbeFields(); + } catch (Exception e) { + log.error( + "Error creating MethodsDataModel: " + e.getMessage()); + } + updateWithFields(); + } + + public MethodsDataModel(Object o, boolean privateVariables) { + methods = new ArrayList(); + targetObj = o; + viewPrivate = privateVariables; + update(viewPrivate); + } + + public void update() { + if (probeFields == null) + update(viewPrivate); + else + updateWithFields(); + } + + public void setViewPrivate(boolean privateVariables) { + viewPrivate = privateVariables; + } + + private void update(boolean privateVariables) { + viewPrivate = privateVariables; + methods.clear(); + + Class cl = targetObj.getClass(); + for (int i = 0; i < deepLevel; i++) + cl = cl.getSuperclass(); + + Method[] meth = cl.getDeclaredMethods(); + AccessibleObject.setAccessible(meth, true); + + for (int i = 0; i < meth.length; i++) + if (viewPrivate || Modifier.isPublic(meth[i].getModifiers())) + methods.add(meth[i]); + } + + public void setDeepLevel(int level) { + deepLevel = level; + } + + private void updateWithFields() { + methods.clear(); + + Class cl = targetObj.getClass(); + + while (cl != null) { + Method[] meth = cl.getDeclaredMethods(); + AccessibleObject.setAccessible(meth, true); + + for (int i = 0; i < meth.length; i++) + if (probeFields.contains(meth[i].getName())) + methods.add(meth[i]); + + cl = cl.getSuperclass(); + } + } + + public int getSize() { + return methods.size(); + } + + public Object getElementAt(int index) { + return methods.get(index); + } + + public void invokeMethodAt(int index) { + Method m = (Method) methods.get(index); + + if (m.getParameterTypes().length > 0) { + JOptionPane.showMessageDialog(null, "Method requires parameters", + "Method result", JOptionPane.INFORMATION_MESSAGE); + return; + } + + try { + Object o = m.invoke(targetObj, null); + + if (o == null) + return; + + JOptionPane.showMessageDialog(null, o.toString(), + "Method result", JOptionPane.PLAIN_MESSAGE); + + } catch (Exception e) { + System.out.println("Error in method.invoke:" + e.getMessage()); + } + } + + public void invokeMethodAt(int index, Object[] params) { + Method m = (Method) methods.get(index); + + try { + Object o = m.invoke(targetObj, params); + + if (o == null) + return; + + JOptionPane.showMessageDialog(null, o.toString(), + "Method result", JOptionPane.PLAIN_MESSAGE); + + } catch (Exception e) { + System.out.println("Error in method.invoke:" + e.getMessage()); + } + } + + public void addListDataListener(ListDataListener l) { + } + + public void removeListDataListener(ListDataListener l) { + } +} diff --git a/src/main/java/microsim/gui/probe/ObjectDataModel.java b/src/main/java/microsim/gui/probe/ObjectDataModel.java new file mode 100644 index 00000000..bbd5ee4a --- /dev/null +++ b/src/main/java/microsim/gui/probe/ObjectDataModel.java @@ -0,0 +1,243 @@ +package microsim.gui.probe; + +import javax.swing.*; +import javax.swing.table.*; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Collection; +import java.util.Iterator; + +import java.lang.reflect.*; + +/** + * Not of interest for users. + * A data model used to contain the list of elements within a collection. + * It is used by the Probe frame. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class ObjectDataModel extends AbstractTableModel { + + private static final Logger log = LogManager.getLogger(ObjectDataModel.class); + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + private static final int COLUMNS = 3; + + private static final int COL_FIELD = 0; + private static final int COL_VALUE = 1; + private static final int COL_TYPE = 2; + + private boolean isAnArray; + Object targetObj; + Object[][] data; + + public ObjectDataModel(Object objToInspect) { + + if (objToInspect.getClass().isArray() || ProbeReflectionUtils.isCollection(objToInspect.getClass())) { + targetObj = objToInspect; + update(); + } else + log.error( + "You were trying to build an ObjectDataModel passing a wrong object type"); + } + + public void update() { + if (isAnArray = targetObj.getClass().isArray()) + updateAnArray(); + else + updateAList(); + } + + private void updateAnArray() { + // Object[] objs = (Object[]) targetObj; + + data = new Object[Array.getLength(targetObj)][COLUMNS]; + for (int i = 0; i < Array.getLength(targetObj); i++) { + Object o = Array.get(targetObj, i); + data[i][COL_VALUE] = o.toString(); + data[i][COL_TYPE] = o.getClass().getName(); + data[i][COL_FIELD] = o; + } + } + + private void updateAList() { + Collection c = (Collection) targetObj; + + data = new Object[c.size()][COLUMNS]; + Iterator itr = c.iterator(); + int i = 0; + while (itr.hasNext()) { + Object o = itr.next(); + data[i][COL_VALUE] = o.toString(); + data[i][COL_TYPE] = o.getClass().getName(); + data[i][COL_FIELD] = o; + i++; + } + } + + public String getHeaderText(int column) { + switch (column + 1) { + case COL_VALUE: + return "Value"; + case COL_TYPE: + return "Type"; + default: + return ""; + } + } + + public int getColumnCount() { + if (data == null) + return 0; + else + return COLUMNS - 1; + } + + public Object getValueAt(int row, int col) { + return data[row][col + 1]; + } + + public int getRowCount() { + if (data == null) + return 0; + else + return data.length; + } + + public void setValueAt(Object val, int row, int col) { + + try { + Object f = data[row][COL_FIELD]; + + if (f == null) { + JOptionPane.showMessageDialog(null, "The variable is null.\n It is impossible to edit.", + "Probe editing variable", JOptionPane.INFORMATION_MESSAGE); + return; + } + + if (ProbeReflectionUtils.isEditable(f.getClass())) + if (isAnArray) + setPrimitiveValueToArray(f, val, row); + else + ProbeReflectionUtils.setValueToObject(f, val); + else { + JOptionPane.showMessageDialog(null, "The variable is not a primitive.\n" + + "To edit its value you can open a probe to it. ", + "Probe editing variable", JOptionPane.INFORMATION_MESSAGE); + return; + } + + } catch (Exception e) { + System.out.println("Error setting field: " + e.getMessage()); + return; + } + // Indicate the change has happened: + data[row][col + 1] = val; + fireTableDataChanged(); + } + + private void setPrimitiveValueToArray(Object o, Object val, int row) { + if (o instanceof String) { + Array.set(targetObj, row, val); + return; + } + if (o instanceof Integer) { + Array.setInt(targetObj, row, Integer.parseInt(val.toString())); + return; + } + if (o instanceof Double) { + Array.setDouble(targetObj, row, Double.parseDouble(val.toString())); + return; + } + if (o instanceof Boolean) { + Array.setBoolean(targetObj, row, Boolean.valueOf(val.toString()).booleanValue()); + return; + } + if (o instanceof Byte) { + Array.setByte(targetObj, row, Byte.parseByte(val.toString())); + return; + } + if (o instanceof Character) { + Array.setChar(targetObj, row, val.toString().charAt(0)); + return; + } + if (o instanceof Float) { + Array.setFloat(targetObj, row, Float.parseFloat(val.toString())); + return; + } + if (o instanceof Long) { + Array.setLong(targetObj, row, Long.parseLong(val.toString())); + return; + } + if (o instanceof Short) { + Array.setShort(targetObj, row, Short.parseShort(val.toString())); + return; + } + } + + public Object getObjectAtRow(int row) { + try { + return data[row][COL_FIELD]; + } catch (Exception e) { + return null; + } + } + + public String getObjectNameAtRow(int row) { + try { + return data[row][COL_FIELD].getClass().getName(); + } catch (Exception e) { + return ""; + } + } + + public boolean isCellEditable(int row, int col) { + // if (!isAnArray) + // return false; + + if ((col + 1) == COL_VALUE) + return true; + else + return false; + + } + + public Object getProbedObject() { + return targetObj; + } +} diff --git a/src/main/java/microsim/gui/probe/PanelObjectCollection.java b/src/main/java/microsim/gui/probe/PanelObjectCollection.java new file mode 100644 index 00000000..ea6610e0 --- /dev/null +++ b/src/main/java/microsim/gui/probe/PanelObjectCollection.java @@ -0,0 +1,123 @@ +package microsim.gui.probe; + +import java.awt.*; +import javax.swing.*; +import java.awt.event.*; + +/** + * Not of interest for users. + * Its the panel containing the table using ObjectDataModel. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class PanelObjectCollection extends JPanel { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + BorderLayout borderLayout = new BorderLayout(); + ObjectDataModel dataModel; + + JButton jBtnNewProbe = new JButton(); + JScrollPane jScrollPaneObjects = new JScrollPane(); + JTable jTableObjects = new JTable(); + + public PanelObjectCollection(Object o) { + try { + dataModel = new ObjectDataModel(o); + jbInit(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + void jbInit() throws Exception { + this.setLayout(borderLayout); + jTableObjects.setModel(dataModel); + jTableObjects.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(MouseEvent e) { + jTableObjects_mouseClicked(e); + } + }); + + jBtnNewProbe.setText("Open probe on selected object"); + jBtnNewProbe.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnNewProbe_actionPerformed(e); + } + }); + + for (int i = 0; i < jTableObjects.getColumnModel().getColumnCount(); i++) + jTableObjects.getColumnModel().getColumn(i).setHeaderValue( + dataModel.getHeaderText(i)); + + this.add(jBtnNewProbe, BorderLayout.SOUTH); + this.add(jScrollPaneObjects, BorderLayout.CENTER); + jScrollPaneObjects.getViewport().add(jTableObjects, null); + } + + void jBtnNewProbe_actionPerformed(ActionEvent e) { + openNewProbe(); + } + + private void openNewProbe() { + if (jTableObjects.getSelectedRow() < 0) { + JOptionPane.showMessageDialog(null, "Please select an element to probe first.", + "Probe an element of a list", JOptionPane.INFORMATION_MESSAGE); + return; + } + + Object o = dataModel.getObjectAtRow(jTableObjects.getSelectedRow()); + if (o == null) { + JOptionPane.showMessageDialog(null, "The selected element is null.", + "Probe an element of a list", JOptionPane.INFORMATION_MESSAGE); + return; + } + String s = dataModel.getObjectNameAtRow(jTableObjects.getSelectedRow()); + ProbeFrame pF = new ProbeFrame(o, this.toString() + "." + s); + pF.setVisible(true); + } + + public void updateList() { + dataModel.update(); + updateUI(); + } + + public Object getProbedObject() { + return dataModel.getProbedObject(); + } + + void jTableObjects_mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) + openNewProbe(); + } +} diff --git a/src/main/java/microsim/gui/probe/ParameterManager.java b/src/main/java/microsim/gui/probe/ParameterManager.java new file mode 100644 index 00000000..8af6985f --- /dev/null +++ b/src/main/java/microsim/gui/probe/ParameterManager.java @@ -0,0 +1,75 @@ +package microsim.gui.probe; + +import java.net.URL; +import java.util.ArrayList; + +/** + * Not of interest for users. + * A data model used to contain the list of elements within a collection. + * It is used by the Probe frame. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class ParameterManager { + URL fileName; + Object targetObject; + ArrayList fields; + + public ParameterManager(Object target, URL filePath) { + targetObject = target; + fileName = filePath; + fields = new ArrayList(); + } + + public void addParameterField(String fieldName) { + boolean flag = true; + for (int i = 0; i < fields.size(); i++) + if (fields.get(i).toString().equals(fieldName)) + flag = false; + + if (flag) + fields.add(fieldName); + } + + public void readParamsFromFile() { + } + + public void readParamsFromObject() { + } + + public void updateParameters() { + } + + public void saveParameters() { + } + +} diff --git a/src/main/java/microsim/gui/probe/ProbeFrame.java b/src/main/java/microsim/gui/probe/ProbeFrame.java new file mode 100644 index 00000000..095dbffa --- /dev/null +++ b/src/main/java/microsim/gui/probe/ProbeFrame.java @@ -0,0 +1,431 @@ +package microsim.gui.probe; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; + +import java.lang.reflect.Method; + +/** + * The probe window class. It is able to inspect content of objects. + * If the probed object implements the IProbeFields interface + * the inspected fields are the only ones specified by the getProbeFields() + * method. Otherwise, the object will be completely inspected. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ + +public class ProbeFrame extends JFrame { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + private String frameName = ""; + private String objectContent = ""; + + private VariableDataModel variables; // = new VariableDataModel(this); + private MethodsDataModel methods; + private List openedPanels; + protected Object probedObject; + + // ImageIcon imageIcon = new ImageIcon( + // ProbeFrame.class.getResource("/jas/images/Find16.gif")); + + // Main Frame + JTabbedPane jTabbedPaneMain = new JTabbedPane(); + JPanel jPanelLowerButtons = new JPanel(); + + // First tab + BorderLayout borderLayout1 = new BorderLayout(); + JPanel jPaneVariables = new JPanel(); + JTable jTableVariables = new JTable(); + JScrollPane jScrollVariables = new JScrollPane(jTableVariables); + JPanel jPaneVariablesButtons = new JPanel(); + JButton jBtnNewProbe = new JButton(); + JButton jBtnListValues = new JButton(); + + // Second tab + BorderLayout borderLayout2 = new BorderLayout(); + JPanel jPaneMethods = new JPanel(); + JList jListMethods = new JList(); + JScrollPane jScrollMethods = new JScrollPane(jListMethods); + JButton jBtnInvoke = new JButton(); + + // Lower buttons + JButton jBtnOK = new JButton(); + JButton jBtnRefresh = new JButton(); + JToggleButton jBtnPrivate = new JToggleButton(); + FlowLayout flowLayout1 = new FlowLayout(); + JPanel jNorthPanel = new JPanel(); + JLabel jObjectName = new JLabel(""); + BorderLayout borderLayout3 = new BorderLayout(); + JComboBox jCmbSuperclass = new JComboBox(); + + /** + * This constructor checks if the given object implements the IProbeFields + * interface. + * + * @param o The object to probe. + * @param name The title of the frame window. + */ + public ProbeFrame(Object o, String name) { + if (o instanceof IProbeFields) { + variables = new VariableDataModel(o); + methods = new MethodsDataModel(o); + setup(o, name); + jBtnPrivate.setVisible(false); + jCmbSuperclass.setVisible(false); + jNorthPanel.setPreferredSize(new Dimension(200, 22)); + } else { + variables = new VariableDataModel(o, true); + methods = new MethodsDataModel(o, true); + setup(o, name); + } + } + + /** + * This constructor ignores the IProbeFields interface and shows all the + * fields of the object. + * + * @param o The object to probe. + * @param name The title of the frame window. + * @param privateFields If true the probe will show only the public + * properties and method. If false it will be shown public, + * protected + * and private fields. + */ + public ProbeFrame(Object o, String name, boolean privateFields) { + variables = new VariableDataModel(o, privateFields); + methods = new MethodsDataModel(o, privateFields); + setup(o, name); + } + + private void setup(Object o, String name) { + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + dispose(); + } + }); + if (o == null) { + this.dispose(); + return; + } + + probedObject = o; + frameName = name; + objectContent = o.getClass().getName() + " (" + o.toString() + ")"; + + jTableVariables.setModel(variables); + jListMethods.setModel(methods); + openedPanels = new ArrayList(); + + try { + jbInit(); + + if (ProbeReflectionUtils.isCollection(o.getClass()) || o.getClass().isArray()) + addCollectionPanel(o); + } catch (Exception e) { + e.printStackTrace(); + } + + Class cl = o.getClass(); + while (cl != null) { + jCmbSuperclass.addItem(cl.getName()); + cl = cl.getSuperclass(); + } + + // setIconImage(imageIcon.getImage()); + } + + /** Show off the frame window. */ + public void dispose() { + probedObject = null; + variables = null; + methods = null; + ; + openedPanels.clear(); + super.dispose(); + } + + private void jbInit() throws Exception { + // Build variable tab + jPaneVariables.setLayout(borderLayout1); + jTableVariables.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); + jTableVariables.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(MouseEvent e) { + jTableVariables_mouseClicked(e); + } + }); + + for (int i = 0; i < jTableVariables.getColumnModel().getColumnCount(); i++) + jTableVariables.getColumnModel().getColumn(i).setHeaderValue( + variables.getHeaderText(i)); + + jBtnNewProbe.setText("Open probe on selected variable"); + jBtnNewProbe.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnNewProbe_actionPerformed(e); + } + }); + jBtnListValues.setText("List values"); + jBtnListValues.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnListValues_actionPerformed(e); + } + }); + jBtnPrivate.setSelected(true); + jBtnPrivate.setText("Private"); + jBtnPrivate.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnPrivate_actionPerformed(e); + } + }); + jListMethods.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(MouseEvent e) { + jListMethods_mouseClicked(e); + } + }); + jBtnPrivate.setSelected(false); + jPaneVariablesButtons.setLayout(flowLayout1); + jObjectName.setText(objectContent); + jNorthPanel.setLayout(borderLayout3); + jCmbSuperclass.setMinimumSize(new Dimension(200, 22)); + jCmbSuperclass.setPreferredSize(new Dimension(200, 22)); + jCmbSuperclass.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jCmbSuperclass_actionPerformed(e); + } + }); + jNorthPanel.setPreferredSize(new Dimension(200, 44)); + jPaneVariablesButtons.add(jBtnListValues, null); + jPaneVariablesButtons.add(jBtnNewProbe, null); + jPaneVariables.add(jPaneVariablesButtons, BorderLayout.SOUTH); + jPaneVariables.add(jScrollVariables, BorderLayout.CENTER); + + // Build methods tab + jPaneMethods.setLayout(borderLayout2); + jBtnInvoke.setText("Execute selected method"); + jBtnInvoke.setVerticalAlignment(SwingConstants.BOTTOM); + jBtnInvoke.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnInvoke_actionPerformed(e); + } + }); + jPaneMethods.add(jBtnInvoke, BorderLayout.SOUTH); + jPaneMethods.add(jScrollMethods, BorderLayout.CENTER); + this.getContentPane().add(jNorthPanel, BorderLayout.NORTH); + + // Build main frame + + jBtnOK.setText("Close"); + jBtnOK.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnOK_actionPerformed(e); + } + }); + jBtnRefresh.setText("Refresh"); + jBtnRefresh.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnRefresh_actionPerformed(e); + } + }); + this.getContentPane().add(jPanelLowerButtons, BorderLayout.SOUTH); + jPanelLowerButtons.add(jBtnPrivate, null); + jPanelLowerButtons.add(jBtnRefresh, null); + jPanelLowerButtons.add(jBtnOK, null); + + this.getContentPane().add(jTabbedPaneMain, BorderLayout.CENTER); + jTabbedPaneMain.add(jPaneVariables, "Variables"); + jTabbedPaneMain.add(jPaneMethods, "Methods"); + jNorthPanel.add(jObjectName, BorderLayout.CENTER); + + setSize(new Dimension(414, 403)); + setLocation(0, 100); + setTitle(frameName); + jNorthPanel.add(jCmbSuperclass, BorderLayout.SOUTH); + } + + void jBtnOK_actionPerformed(ActionEvent e) { + this.dispose(); + } + + void jBtnRefresh_actionPerformed(ActionEvent e) { + refreshData(); + } + + private void refreshData() { + variables.update(); + jTableVariables.updateUI(); + methods.update(); + jListMethods.updateUI(); + + for (int i = 0; i < openedPanels.size(); i++) + ((PanelObjectCollection) openedPanels.get(i)).updateList(); + + } + + void jBtnNewProbe_actionPerformed(ActionEvent e) { + openNewProbe(); + } + + void jBtnInvoke_actionPerformed(ActionEvent e) { + invokeMethod(); + } + + void jBtnListValues_actionPerformed(ActionEvent e) { + if (jTabbedPaneMain.getSelectedComponent() != jPaneVariables) + return; + + if (jTableVariables.getSelectedRow() < 0) { + JOptionPane.showMessageDialog(null, "Please select a variable to list first.", + "Probe variable", JOptionPane.INFORMATION_MESSAGE); + return; + } + + Object o = variables.getObjectAtRow(jTableVariables.getSelectedRow()); + if (o == null) { + JOptionPane.showMessageDialog(null, "The selected variable is null.", + "Probe variable", JOptionPane.INFORMATION_MESSAGE); + return; + } + if (ProbeReflectionUtils.isCollection(o.getClass()) || o.getClass().isArray()) { + String s = variables.getObjectNameAtRow(jTableVariables.getSelectedRow()); + addCollectionPanel(o, s); + } else { + JOptionPane.showMessageDialog(null, "The selected variable is not a collection.", + "Probe variable", JOptionPane.INFORMATION_MESSAGE); + return; + } + } + + private void addCollectionPanel(Object o) { + PanelObjectCollection pn = new PanelObjectCollection(o); + jTabbedPaneMain.add(pn, "List values", 0); + openedPanels.add(pn); + } + + private void addCollectionPanel(Object o, String s) { + Iterator it = openedPanels.iterator(); + while (it.hasNext()) + if (((PanelObjectCollection) it.next()).getProbedObject() == o) + return; + + PanelObjectCollection pn = new PanelObjectCollection(o); + jTabbedPaneMain.add(pn, s); + jTabbedPaneMain.setSelectedIndex(jTabbedPaneMain.getTabCount() - 1); + openedPanels.add(pn); + } + + void jBtnPrivate_actionPerformed(ActionEvent e) { + variables.setViewPrivate(jBtnPrivate.isSelected()); + methods.setViewPrivate(jBtnPrivate.isSelected()); + refreshData(); + } + + private void openNewProbe() { + if (jTabbedPaneMain.getSelectedComponent() != jPaneVariables) + return; + + if (jTableVariables.getSelectedRow() < 0) { + JOptionPane.showMessageDialog(null, "Please select a variable to probe first.", + "Probe variable", JOptionPane.INFORMATION_MESSAGE); + return; + } + + Object o = variables.getObjectAtRow(jTableVariables.getSelectedRow()); + if (o == null) { + JOptionPane.showMessageDialog(null, "The selected variable is null.", + "Probe variable", JOptionPane.INFORMATION_MESSAGE); + return; + } + String s = variables.getObjectNameAtRow(jTableVariables.getSelectedRow()); + ProbeFrame pF = new ProbeFrame(o, this.getTitle() + "." + s); + pF.setVisible(true); + } + + private void invokeMethod() { + Object[] params = {}; + if (jListMethods.getSelectedIndex() == -1) + return; + + Method m = (Method) methods.getElementAt(jListMethods.getSelectedIndex()); + if (!ProbeReflectionUtils.isAnExecutableMethod(m)) { + JOptionPane.showMessageDialog(null, + "Sorry but this method requires complex arguments.\nThis function is not yet implemented.", + "Invoke method", JOptionPane.INFORMATION_MESSAGE); + return; + } + + if (m.getParameterTypes().length > 0) { + MethodDialog md = new MethodDialog(null, "P", true, m); + md.setVisible(true); + if (md.cancel) + return; + params = md.getParameters(); + } + + if (jListMethods.getSelectedIndex() < 0) { + JOptionPane.showMessageDialog(null, "Please select a method to invoke first.", + "Invoke method", JOptionPane.INFORMATION_MESSAGE); + return; + } + + if (m.getParameterTypes().length > 0) + methods.invokeMethodAt(jListMethods.getSelectedIndex(), params); + else + methods.invokeMethodAt(jListMethods.getSelectedIndex()); + } + + void jTableVariables_mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) + openNewProbe(); + } + + void jListMethods_mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) + invokeMethod(); + } + + void jCmbSuperclass_actionPerformed(ActionEvent e) { + if (jCmbSuperclass.getSelectedIndex() < 0) + return; + variables.setDeepLevel(jCmbSuperclass.getSelectedIndex()); + methods.setDeepLevel(jCmbSuperclass.getSelectedIndex()); + refreshData(); + + } + +} diff --git a/src/main/java/microsim/gui/probe/ProbeReflectionUtils.java b/src/main/java/microsim/gui/probe/ProbeReflectionUtils.java new file mode 100644 index 00000000..de28d70f --- /dev/null +++ b/src/main/java/microsim/gui/probe/ProbeReflectionUtils.java @@ -0,0 +1,173 @@ +package microsim.gui.probe; + +import java.lang.reflect.*; + +/** + * A collection of static methods using the java reflection + * to manipulate objects. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class ProbeReflectionUtils { + + /** + * Test if the given class implements the java.util.Collection interface. + * The whole hierarchy of the class is tested. + * + * @param f The class to be tested. + * @return True if the class is a Collection, false otherwise. + */ + public static boolean isCollection(Class f) { + Class[] intf = f.getInterfaces(); + boolean flag = false; + + for (int i = 0; i < intf.length && !flag; i++) { + if (intf[i].getName() == "java.util.Collection") + flag = true; + else if (intf[i].getInterfaces().length > 0) + if (isCollection(intf[i])) + flag = true; + } + + return flag; + } + + /** + * Test if the given class is a wrapper for a native type or a string. + * It could be a Double, Long, Integer, Float, String, Character or Boolean. + * + * @param c The class to be tested. + * @return True if class is of a native type, false in any other case. + */ + public static boolean isEditable(Class c) { + if (c.getSuperclass() == null) + return false; + + return ((c.getSuperclass().getName().equals("java.lang.Number")) || + (c.getName().equals("java.lang.String")) || + (c.getName().equals("java.lang.Character")) || + (c.getName().equals("java.lang.Boolean"))); + } + + /** + * Test if the given object is a wrapper for a native type or a string. + * It could be a Byte, Double, Long, Short, Integer, Float, String, + * Character or Boolean. + * + * @param o The object to be tested. + * @return True if class is of a native type, false in any other case. + */ + public static boolean isEditable(Object o) { + return isEditable(o.getClass()); + } + + /** + * Test if the given method requires parameters of a native type. + * In this case the method can be executed. + * + * @param m The method to be tested. + * @return True if the method requires only native-type parameters. + * A parameter is native type if int, long, ... or its corresponding + * wrapper class (Integer, Double, Long, ...). + */ + public static boolean isAnExecutableMethod(Method m) { + Class[] cl = m.getParameterTypes(); + + for (int i = 0; i < cl.length; i++) + if (!isEditable(cl[i]) && !cl[i].isPrimitive()) + return false; + + return true; + } + + /** + * Set a given value wrapped by an Object into the wrapper object. + * + * @param o The object to be updated. It must be of a native wrapper class + * (String, Double, Long, ...). + * @param val An object whose toString() method return a valid format for + * the class type of object o. + */ + public static void setValueToObject(Object o, Object val) { + try { + + Field f = o.getClass().getDeclaredField("value"); + f.setAccessible(true); + + if (o instanceof String) { + char[] ch = new char[val.toString().toCharArray().length]; + System.arraycopy(val.toString().toCharArray(), 0, ch, 0, ch.length); + f.set(o, (Object) ch); + + f = o.getClass().getDeclaredField("count"); + f.setAccessible(true); + f.set(o, new Integer(ch.length)); + + return; + } + if (o instanceof Integer) { + f.setInt(o, Integer.parseInt(val.toString())); + return; + } + if (o instanceof Double) { + f.setDouble(o, Double.parseDouble(val.toString())); + return; + } + if (o instanceof Boolean) { + f.setBoolean(o, Boolean.valueOf(val.toString()).booleanValue()); + return; + } + if (o instanceof Byte) { + f.setByte(o, Byte.parseByte(val.toString())); + return; + } + if (o instanceof Character) { + f.setChar(o, val.toString().charAt(0)); + return; + } + if (o instanceof Float) { + f.setFloat(o, Float.parseFloat(val.toString())); + return; + } + if (o instanceof Long) { + f.setLong(o, Long.parseLong(val.toString())); + return; + } + if (o instanceof Short) { + f.setShort(o, Short.parseShort(val.toString())); + return; + } + } catch (Exception e) { + System.out.println("Err:" + e.getMessage()); + } + } +} diff --git a/src/main/java/microsim/gui/probe/VariableDataModel.java b/src/main/java/microsim/gui/probe/VariableDataModel.java new file mode 100644 index 00000000..7549a548 --- /dev/null +++ b/src/main/java/microsim/gui/probe/VariableDataModel.java @@ -0,0 +1,316 @@ +package microsim.gui.probe; + +import javax.swing.*; +import javax.swing.table.*; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.lang.reflect.*; + +import java.util.List; + +/** + * Not of interest for users. + * A data model used to show the list of variables into the probe. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class VariableDataModel extends AbstractTableModel { + + private static final Logger log = LogManager.getLogger(VariableDataModel.class); + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + private static final int COLUMNS = 4; + + private static final int COL_FIELD = 0; + private static final int COL_NAME = 1; + private static final int COL_TYPE = 2; + private static final int COL_VALUE = 3; + + private Object targetObj; + private Object[][] data; + private boolean viewPrivate; + private List probeFields; + + private int deepLevel = 0; + + public VariableDataModel(Object objToInspect) { + targetObj = objToInspect; + viewPrivate = true; + try { + probeFields = ((IProbeFields) objToInspect).getProbeFields(); + } catch (Exception e) { + log.error("Error creating VariableDataModel: " + e.getMessage()); + } + updateWithFields(); + } + + public VariableDataModel(Object objToInspect, boolean privateVariables) { + targetObj = objToInspect; + viewPrivate = privateVariables; + probeFields = null; + update(viewPrivate); + } + + private void updateWithFields() { + Class cl = targetObj.getClass(); + Field[] fields; + int k = 0; + + // Searching for rowCount + while (cl != null) { + fields = cl.getDeclaredFields(); + AccessibleObject.setAccessible(fields, true); + + for (int i = 0; i < fields.length; i++) + if (probeFields.contains(fields[i].getName())) + k++; + + cl = cl.getSuperclass(); + } + + // Now fill up the fields + data = new Object[k][COLUMNS]; + cl = targetObj.getClass(); + k = 0; + while (cl != null) { + fields = cl.getDeclaredFields(); + AccessibleObject.setAccessible(fields, true); + + for (int i = 0; i < fields.length; i++) + if (probeFields.contains(fields[i].getName())) { + Field f = fields[i]; + data[k][COL_NAME] = f.getName(); + data[k][COL_TYPE] = f.getType(); + + try { + Object o = f.get(targetObj); + if (o != null) { + data[k][COL_FIELD] = f; + if (ProbeReflectionUtils.isCollection(f.getType()) || o.getClass().isArray()) + data[k][COL_VALUE] = "[...]"; + else + data[k][COL_VALUE] = o.toString(); + } + } catch (Exception e) { + System.out.println("Error in field :" + e.getMessage()); + } + ; + k++; + } + + cl = cl.getSuperclass(); + } + } + + public void update() { + if (probeFields == null) + update(viewPrivate); + else + updateWithFields(); + } + + public void setViewPrivate(boolean privateVariables) { + viewPrivate = privateVariables; + } + + public void setDeepLevel(int level) { + deepLevel = level; + } + + private void update(boolean privateVariables) { + Class cl = targetObj.getClass(); + for (int i = 0; i < deepLevel; i++) + cl = cl.getSuperclass(); + + Field[] fields; + if (viewPrivate) + fields = cl.getDeclaredFields(); + else + fields = cl.getFields(); + AccessibleObject.setAccessible(fields, true); + + data = new Object[fields.length][COLUMNS]; + for (int i = 0; i < fields.length; i++) { + Field f = fields[i]; + data[i][COL_NAME] = f.getName(); + data[i][COL_TYPE] = f.getType(); + + try { + Object o = f.get(targetObj); + if (o != null) { + data[i][COL_FIELD] = f; + if (ProbeReflectionUtils.isCollection(f.getType()) || o.getClass().isArray()) + data[i][COL_VALUE] = "[...]"; + else + data[i][COL_VALUE] = o.toString(); + } + } catch (Exception e) { + System.out.println("Error in field :" + e.getMessage()); + } + ; + } + } + + public String getHeaderText(int column) { + switch (column + 1) { + case COL_NAME: + return "Name"; + case COL_TYPE: + return "Type"; + case COL_VALUE: + return "Value"; + default: + return ""; + } + } + + public int getColumnCount() { + if (data == null) + return 0; + else + return COLUMNS - 1; + } + + public Object getValueAt(int row, int col) { + return data[row][col + 1]; + } + + public int getRowCount() { + if (data == null) + return 0; + else + return data.length; + } + + public void setValueAt(Object val, int row, int col) { + + try { + Field f = (Field) data[row][COL_FIELD]; + + if (f == null) { + JOptionPane.showMessageDialog(null, "The variable is null.\n It is impossible to edit.", + "Probe editing variable", JOptionPane.INFORMATION_MESSAGE); + return; + } + + if (f.getType().isPrimitive() || f.getType().getName().equals("java.lang.String")) + setPrimitiveValueToClass(f.getType(), val, f); + else { + JOptionPane.showMessageDialog(null, "The variable is not a primitive.\n" + + "To edit its value you can open a probe to it. ", + "Probe editing variable", JOptionPane.INFORMATION_MESSAGE); + return; + } + + } catch (Exception e) { + System.out.println("Error setting field: " + e.getMessage()); + return; + } + // Indicate the change has happened: + data[row][col + 1] = val; + fireTableDataChanged(); + } + + private void setPrimitiveValueToClass(Class cl, Object val, Field f) { + try { + if (cl.getName().equals("java.lang.String")) { + f.set(targetObj, val.toString()); + return; + } + if (cl == Integer.TYPE) { + f.setInt(targetObj, Integer.parseInt(val.toString())); + return; + } + if (cl == Double.TYPE) { + f.setDouble(targetObj, Double.parseDouble(val.toString())); + return; + } + if (cl == Boolean.TYPE) { + f.setBoolean(targetObj, Boolean.valueOf(val.toString()).booleanValue()); + return; + } + if (cl == Byte.TYPE) { + f.setByte(targetObj, Byte.parseByte(val.toString())); + return; + } + if (cl == Character.TYPE) { + f.setChar(targetObj, val.toString().charAt(0)); + return; + } + if (cl == Float.TYPE) { + f.setFloat(targetObj, Float.parseFloat(val.toString())); + return; + } + if (cl == Long.TYPE) { + f.setLong(targetObj, Long.parseLong(val.toString())); + return; + } + if (cl == Short.TYPE) { + f.setShort(targetObj, Short.parseShort(val.toString())); + return; + } + } catch (Exception e) { + System.out.println(e.getMessage()); + } + return; + } + + public Object getObjectAtRow(int row) { + try { + return ((Field) data[row][COL_FIELD]).get(targetObj); + } catch (Exception e) { + return null; + } + } + + public String getObjectNameAtRow(int row) { + try { + return ((Field) data[row][COL_FIELD]).getName(); + } catch (Exception e) { + return ""; + } + } + + public boolean isCellEditable(int row, int col) { + if ((col + 1) == COL_VALUE) + return true; + else + return false; + + } +} diff --git a/src/main/java/microsim/gui/shell/AboutFrame.java b/src/main/java/microsim/gui/shell/AboutFrame.java new file mode 100644 index 00000000..48543234 --- /dev/null +++ b/src/main/java/microsim/gui/shell/AboutFrame.java @@ -0,0 +1,214 @@ +package microsim.gui.shell; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.io.BufferedInputStream; +import java.util.Enumeration; +import java.util.Properties; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.border.TitledBorder; + +import org.apache.commons.io.IOUtils; + +/** + * The about frame of the JAS application. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class AboutFrame extends JFrame { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + ImageIcon imageIcon = new ImageIcon(java.awt.Toolkit.getDefaultToolkit() + .getImage( + getClass().getResource( + "/microsim/gui/icons/msIco.gif"))); + + javax.swing.JTabbedPane jTabbedPane = null; + javax.swing.JPanel jMainPanel = null; + + javax.swing.JPanel jLicencePanel = null; + javax.swing.JScrollPane jScrollLicence = null; + javax.swing.JTextArea jLicenceText = null; + + javax.swing.JPanel jSystemPanel = null; + javax.swing.JTable jSystemTable = null; + javax.swing.JScrollPane jSystemScroll = null; + + javax.swing.JPanel jLibrariesPanel = null; + javax.swing.JScrollPane jLibrariesScroll = null; + javax.swing.JTable jLibrariesTable = null; + + private javax.swing.JPanel jContentPane = null; + + public AboutFrame() { + initialize(); + } + + private void initialize() { + this.setContentPane(getJContentPane()); + setIconImage(imageIcon.getImage()); + Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation((int) ((d.getWidth() - 400) / 2), + (int) ((d.getHeight() - 400) / 2)); + setSize(450, 450); + setTitle("About JAS-mine"); + } + + public javax.swing.JTabbedPane getJTabbedPane() { + if (jTabbedPane == null) { + jTabbedPane = new JTabbedPane(); + jTabbedPane.setBorder(new TitledBorder("")); + jTabbedPane.add(getJMainPanel(), "About JAS-mine"); + jTabbedPane.add(getJLicensePanel(), "License"); + // jTabbedPane.add(getJLibrariesPanel(), "Libraries"); + jTabbedPane.add(getJSystemPanel(), "System"); + } + return jTabbedPane; + } + + public javax.swing.JPanel getJMainPanel() { + if (jMainPanel == null) { + jMainPanel = new AboutPanel(); + } + return jMainPanel; + } + + public javax.swing.JPanel getJLicensePanel() { + if (jLicencePanel == null) { + jLicencePanel = new JPanel(); + jLicencePanel.setLayout(new BorderLayout()); + jLicencePanel.setBorder(BorderFactory.createEtchedBorder()); + jLicencePanel.add(getJScollLicensePanel(), BorderLayout.CENTER); + } + return jLicencePanel; + } + + public javax.swing.JScrollPane getJScollLicensePanel() { + if (jScrollLicence == null) { + jScrollLicence = new JScrollPane(); + jScrollLicence.setViewportView(getJLicenceText()); + } + return jScrollLicence; + } + + private String getLicence() { + try { + final BufferedInputStream bis = new BufferedInputStream( + AboutFrame.class.getResourceAsStream("/jasmine_license.txt")); + return IOUtils.toString(bis); + + } catch (Exception e) { + return "WARNING: No licence file found!"; + } + } + + public javax.swing.JTextArea getJLicenceText() { + if (jLicenceText == null) { + jLicenceText = new JTextArea(); + jLicenceText.setText(getLicence()); + jLicenceText.setCaretPosition(0); + } + return jLicenceText; + } + + public javax.swing.JPanel getJSystemPanel() { + if (jSystemPanel == null) { + jSystemPanel = new JPanel(); + jSystemPanel.setBorder(BorderFactory.createEtchedBorder()); + jSystemPanel.setLayout(new BorderLayout()); + jSystemPanel.add(getJSystemScroll(), BorderLayout.CENTER); + } + return jSystemPanel; + } + + public javax.swing.JTable getJSystemTable() { + if (jSystemTable == null) { + jSystemTable = new JTable(getSystem(), getSystemCols()); + } + return jSystemTable; + } + + public javax.swing.JScrollPane getJSystemScroll() { + if (jSystemScroll == null) { + jSystemScroll = new JScrollPane(); + jSystemScroll.setViewportView(getJSystemTable()); + } + return jSystemScroll; + } + + private Object[] getSystemCols() { + return new Object[] { "Variable", "Value" }; + } + + private Object[][] getSystem() { + Properties sysProp = System.getProperties(); + Object[][] systemProps = new Object[sysProp.size() + 2][2]; + + systemProps[0][0] = "JVM total memory"; + systemProps[0][1] = (Runtime.getRuntime().totalMemory() / 1024) + " Kb"; + systemProps[1][0] = "Used JVM memory"; + systemProps[1][1] = (Runtime.getRuntime().freeMemory() / 1024) + " Kb"; + + Enumeration enumItem = sysProp.propertyNames(); + int i = 2; + while (enumItem.hasMoreElements()) { + String key = (String) enumItem.nextElement(); + systemProps[i][0] = key; + systemProps[i][1] = sysProp.getProperty(key); + i++; + } + + return systemProps; + } + + /** + * This method initializes jContentPane + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getJContentPane() { + if (jContentPane == null) { + jContentPane = new javax.swing.JPanel(); + jContentPane.setLayout(new java.awt.BorderLayout()); + jContentPane.add(getJTabbedPane(), java.awt.BorderLayout.CENTER); + } + return jContentPane; + } +} diff --git a/src/main/java/microsim/gui/shell/AboutPanel.java b/src/main/java/microsim/gui/shell/AboutPanel.java new file mode 100644 index 00000000..76d72e40 --- /dev/null +++ b/src/main/java/microsim/gui/shell/AboutPanel.java @@ -0,0 +1,118 @@ +package microsim.gui.shell; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.swing.ImageIcon; +import javax.swing.JPanel; + +/** + * The panel used by AboutFrame window. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class AboutPanel extends JPanel { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + BorderLayout borderLayout1 = new BorderLayout(); + + ImageIcon imageJAS = new ImageIcon( + java.awt.Toolkit.getDefaultToolkit().getImage( + getClass().getResource("/microsim/gui/icons/logo_2.png"))); + + /** Create a new about panel. */ + public AboutPanel() { + try { + jbInit(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + void jbInit() throws Exception { + this.setLayout(borderLayout1); + } + + /** + * Draw the panel content. + * + * @param g The graphic device context. + */ + public void paintComponent(Graphics g) { + super.paintComponent(g); + + g.setColor(Color.white); + g.fillRect(0, 0, getWidth(), getHeight()); + + int leftCorner = 0; + int upperCorner = 0; + int areaHeight = imageJAS.getIconHeight() + 80; + + if (getWidth() > imageJAS.getIconWidth()) + leftCorner = (getWidth() - imageJAS.getIconWidth() - 40) / 2; + + if (getHeight() > areaHeight) + upperCorner = (getHeight() - areaHeight) / 2; + + g.drawImage(imageJAS.getImage(), leftCorner, upperCorner, + imageJAS.getIconWidth(), imageJAS.getIconHeight(), this); + + g.setColor(Color.black); + Font font = new Font("Arial", Font.BOLD, 12); + Font font2 = new Font("Script", Font.BOLD, 12); + g.setFont(font); + // int start = 160 + upperCorner; + int start = 30 + upperCorner; + leftCorner += 160; + g.drawString("JAS-mine", leftCorner + 10, start + 10); + + SimpleDateFormat sdf = new SimpleDateFormat("yy"); + g.drawString("Copyright (C) 2014-" + sdf.format(new Date()) + " Ross E. Richardson", leftCorner + 10, + start + 30); + g.drawString("& Matteo Richiardi", leftCorner + 135, start + 50); + g.setFont(font2); + g.drawString("https://github.com/jasmineRepo", leftCorner + 10, start + 70); + // g.setFont(font2); + g.setColor(new Color(63, 0xc3, 0xe7)); + g.drawString("http://www.jas-mine.net", leftCorner + 10, start + 90); + g.setColor(Color.black); + g.setFont(font); + g.drawString("Distributed under GNU Lesser General Public License", leftCorner - 40, start + 110); + } +} diff --git a/src/main/java/microsim/gui/shell/CaptureConsoleWindow.java b/src/main/java/microsim/gui/shell/CaptureConsoleWindow.java new file mode 100644 index 00000000..ee92d61b --- /dev/null +++ b/src/main/java/microsim/gui/shell/CaptureConsoleWindow.java @@ -0,0 +1,281 @@ +package microsim.gui.shell; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; + +import javax.swing.ImageIcon; +import javax.swing.JFileChooser; +import javax.swing.JInternalFrame; +import javax.swing.JOptionPane; +import javax.swing.JScrollPane; +import javax.swing.filechooser.FileFilter; + +/** + * An independent frame that is able to + * grab System.out and System.err streams, + * showing their content in a window. It is useful when the application + * is launched with javaw.exe command, without terminal console. + *

+ * It is possible to save the output in a file. + *

+ * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class CaptureConsoleWindow extends JInternalFrame { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + ImageIcon imageIcon = new ImageIcon( + CaptureConsoleWindow.class.getResource("/microsim/gui/icons/console.gif")); + + ConsoleTextArea cta = null; + javax.swing.JScrollPane jScrollText = null; + + private javax.swing.JToolBar jToolBar = null; + private javax.swing.JButton jBtnClear = null; + private javax.swing.JButton jBtnSave = null; + private javax.swing.JToggleButton jBtnRead = null; + + private javax.swing.JPanel jContentPane = null; + + public CaptureConsoleWindow() { + initialize(); + } + + private void initialize() { + this.setContentPane(getJContentPane()); + this.setSize(508, 263); + this.setFrameIcon(new javax.swing.ImageIcon(getClass().getResource("/microsim/gui/icons/tree.gif"))); + this.setTitle("Output stream"); + this.setResizable(true); + this.setMaximizable(false); + this.setIconifiable(false); + } + + private ConsoleTextArea getJConsoleTextArea() { + if (cta == null) { + try { + cta = new ConsoleTextArea(); + cta.setFont(java.awt.Font.decode("monospaced")); + } catch (Exception e) { + e.printStackTrace(); + } + } + return cta; + } + + private javax.swing.JScrollPane getJScrollText() { + if (jScrollText == null) { + jScrollText = new JScrollPane(getJConsoleTextArea()); + } + return jScrollText; + } + + private void saveText() { + JFileChooser jfc = new JFileChooser(new File(".")); + FileFilter ff = new FileFilter() { + public boolean accept(File f) { + return (f.getName().toLowerCase().endsWith(".txt") || + f.isDirectory()); + } + + public String getDescription() { + return "Text file (.txt)"; + } + }; + jfc.setFileFilter(ff); + + int result = jfc.showSaveDialog(this); + if (result == JFileChooser.CANCEL_OPTION) + return; + + try { + BufferedWriter f = new BufferedWriter(new FileWriter(jfc.getSelectedFile())); + // f.write(); + + // PrintWriter outp = new PrintWriter( + // new FileWriter(jfc.getSelectedFile()) ); + // outp.println(cta.getText()); + // outp.close(); + + // for (int i = 0; i < cta.getRows(); i++) + f.write(cta.getText()); + + f.close(); + } catch (Exception err) { + // String msg = new String("Error writing file:\n" + // + err.getMessage()); + String msg = "Error writing file:\n" // Modification by Ross (See J. Bloch "Effective Java" 2nd Edition, + // Item 5) + + err.getMessage(); + JOptionPane.showMessageDialog(this, msg, + "Error", JOptionPane.ERROR_MESSAGE); + } + + return; + + } + + public void dispose() { + cta.dispose(); + } + + private void clearText() { + cta.setText(""); + } + + /* + * private void dump() + * { + * System.out.println("The java virtual machine has " + + * (Runtime.getRuntime().totalMemory() / 1024) + * + "Kb of memory."); + * + * System.out.println("The current amount of free memory is " + + * (Runtime.getRuntime().freeMemory() / 1024) + * + " Kb."); + * Properties sysProp = System.getProperties(); + * Enumeration enumItem = sysProp.propertyNames(); + * while (enumItem.hasMoreElements()) + * { + * String key = (String) enumItem.nextElement(); + * System.out.println(key + "=" + sysProp.getProperty(key)); + * } + * } + */ + + /** + * This method initializes jToolBar + * + * @return javax.swing.JToolBar + */ + private javax.swing.JToolBar getJToolBar() { + if (jToolBar == null) { + jToolBar = new javax.swing.JToolBar(); + jToolBar.add(getJBtnClear()); + jToolBar.addSeparator(); + jToolBar.add(getJBtnSave()); + jToolBar.addSeparator(); + jToolBar.add(getJBtnRead()); + } + return jToolBar; + } + + /** + * This method initializes jBtnClear + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getJBtnClear() { + if (jBtnClear == null) { + jBtnClear = new javax.swing.JButton(); + jBtnClear.setIcon(new ImageIcon(getClass().getResource("/microsim/gui/icons/clear16.gif"))); + jBtnClear.setToolTipText("Clear the content of the window"); + jBtnClear.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + clearText(); + } + }); + } + return jBtnClear; + } + + /** + * This method initializes jBtnSave + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getJBtnSave() { + if (jBtnSave == null) { + jBtnSave = new javax.swing.JButton(); + jBtnSave.setIcon(new ImageIcon(getClass().getResource("/microsim/gui/icons/Save16.gif"))); + jBtnSave.setToolTipText("Save the text"); + jBtnSave.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + saveText(); + } + }); + } + return jBtnSave; + } + + public void changeReadingStatus() { + if (cta.isReading()) + cta.stopReading(); + else + cta.startReading(); + + jBtnRead.setSelected(cta.isReading()); + } + + /** + * This method initializes jBtnRead + * + * @return javax.swing.JToggleButton + */ + private javax.swing.JToggleButton getJBtnRead() { + if (jBtnRead == null) { + jBtnRead = new javax.swing.JToggleButton(); + jBtnRead.setIcon(new javax.swing.ImageIcon(getClass().getResource("/microsim/gui/icons/view.gif"))); + jBtnRead.setToolTipText("Enable/disable output stream listening"); + jBtnRead.setSelected(true); + jBtnRead.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + changeReadingStatus(); + } + }); + } + return jBtnRead; + } + + /** + * This method initializes jContentPane + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getJContentPane() { + if (jContentPane == null) { + jContentPane = new javax.swing.JPanel(); + jContentPane.setLayout(new java.awt.BorderLayout()); + jContentPane.add(getJScrollText(), java.awt.BorderLayout.CENTER); + jContentPane.add(getJToolBar(), java.awt.BorderLayout.NORTH); + } + return jContentPane; + } + + public void log(String message) { + cta.log(message); + } +} // @jve:visual-info decl-index=0 visual-constraint="10,10" diff --git a/src/main/java/microsim/gui/shell/ConsoleTextArea.java b/src/main/java/microsim/gui/shell/ConsoleTextArea.java new file mode 100644 index 00000000..32bd64f2 --- /dev/null +++ b/src/main/java/microsim/gui/shell/ConsoleTextArea.java @@ -0,0 +1,120 @@ +package microsim.gui.shell; + +import java.io.*; +import javax.swing.*; + +/** + * Internal component of the CaptureConsoleWindow. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa (taken from an example of Byte review). + *

+ */ +public class ConsoleTextArea extends JTextArea { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + // private final LoopedStreams ls; + private PrintStream oldOut, oldErr; + private boolean keepRunning = true; + private ByteArrayOutputStream byteArrayOS = new ByteArrayOutputStream(); + + /** + * It is attached to the default System.out and System.err streams. + * + * @throws IOException + * Thrown in case of stream error. + */ + public ConsoleTextArea() throws IOException { + // Redirect System.out & System.err. + PrintStream ps = new PrintStream(byteArrayOS); + oldOut = System.out; + oldErr = System.err; + System.setOut(ps); + System.setErr(ps); + + startByteArrayReaderThread(); + } + + public void stopReading() { + keepRunning = false; + } + + public void startReading() { + keepRunning = true; + } + + public boolean isReading() { + return keepRunning; + } + + /** Release the captured streams. */ + public void dispose() { + System.setOut(oldOut); + System.setErr(oldErr); + + } + + public synchronized void log(String message) { + append(message + "\n"); + setCaretPosition(getDocument().getLength()); + } + + private void startByteArrayReaderThread() { + new Thread(new Runnable() { + public void run() { + String buff = ""; + while (true) { + // Check for bytes in the stream. + if (byteArrayOS.size() > 0) { + if (keepRunning) { + synchronized (byteArrayOS) { + buff = byteArrayOS.toString(); + byteArrayOS.reset(); + } + append(buff); + setCaretPosition(getDocument().getLength()); + } else { + synchronized (byteArrayOS) { + byteArrayOS.reset(); // Clear the buffer. + } + } + } else + // No data available, go to sleep. + try { + // Check the ByteArrayOutputStream every + // 1 second for new data. + Thread.sleep(500); + Thread.yield(); + } catch (InterruptedException e) { + } + } + } + }).start(); + } + +} diff --git a/src/main/java/microsim/gui/shell/DatabaseExplorerFrame.java b/src/main/java/microsim/gui/shell/DatabaseExplorerFrame.java new file mode 100644 index 00000000..c266784e --- /dev/null +++ b/src/main/java/microsim/gui/shell/DatabaseExplorerFrame.java @@ -0,0 +1,303 @@ +package microsim.gui.shell; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystemException; +import java.sql.SQLException; + +import javax.swing.DefaultListModel; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JInternalFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import microsim.data.db.DatabaseUtils; +import microsim.engine.SimulationEngine; + +import org.h2.tools.Console; + +/** + * Not of interest for users. The frame that controls engine parameters. It is + * shown when the 'Show engine status' menu item of the Control Panel is + * choosen. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class DatabaseExplorerFrame extends JInternalFrame { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + ImageIcon imageMiniPreferences = new ImageIcon(getClass().getResource( + "/microsim/gui/icons/db.gif")); + + JButton jBtnClose = null; + JButton jBtnDelete = null; + JButton jBtnApply = null; + JButton jBtnInit = null; + JPanel jPanelProperties = null; + JPanel jPanelButtons = null; + + JList jList = null; + File[] dirs = null; + DefaultListModel model = new DefaultListModel(); + + private javax.swing.JPanel mainContentPane = null; + + /** + * Constructor. + * + * @param engine + * The simulation engine to edit. + */ + public DatabaseExplorerFrame(SimulationEngine engine) { + initialize(); + } + + private void initialize() { + // setIconImage(imageMiniPreferences.getImage()); + JScrollPane scrollPane = new JScrollPane(getMainContentPane()); + this.setContentPane(scrollPane); + this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + setSize(new Dimension(450, 338)); + setTitle("Database Explorer"); + this.setResizable(true); + + } + + private JPanel getJPanelProperties() { + if (jPanelProperties == null) { + jPanelProperties = new JPanel(); + jPanelProperties.setLayout(new BorderLayout()); + + jPanelProperties.add(new JLabel("Remember to disconnect database to come back"), BorderLayout.NORTH); + jPanelProperties.add(getJList(), BorderLayout.CENTER); + + } + return jPanelProperties; + } + + private JList getJList() { + if (jList == null) { + + File outputDir = new File("output"); + // File[] dirs = outputDir.listFiles(); + dirs = outputDir.listFiles(); + if (dirs == null) + dirs = new File[0]; + // String[] outDirs = new String[dirs.length + 1]; + // outDirs[0] = "INPUT"; + model.addElement("INPUT"); + for (int i = 0; i < dirs.length; i++) { + File file = dirs[i]; + // outDirs[i + 1] = file.getName(); + model.addElement(file.getName()); + } + + // jList = new JList(outDirs); + jList = new JList(model); + } + return jList; + } + + void jBtnApply_actionPerformed(ActionEvent e) { + if (jList.getSelectedValue() == null) + return; + + try { + if (jList.getSelectedIndex() == 0) // Added ";MVCC=TRUE;DB_CLOSE_ON_EXIT=TRUE;FILE_LOCK=NO" in order to + // allow input database to be inspected, closed and then the simulation + // to be run. Without this, an exception is thrown as the database is + // still connected. + new Console() + .runTool(new String[] { "-url", "jdbc:h2:file:./input/input;DB_CLOSE_ON_EXIT=TRUE;FILE_LOCK=NO", + "-user", "sa", "-password", "" }); + else + new Console() + .runTool(new String[] { "-url", "jdbc:h2:file:./output/" + jList.getSelectedValue().toString() + + "/database/out;AUTO_SERVER=TRUE", "-user", "sa", "-password", "" }); + } catch (SQLException e1) { + e1.printStackTrace(); + } + } + + void jBtnDelete_actionPerformed(ActionEvent e) { + if (jList.getSelectedValue() == null) + return; + + try { + if (jList.getSelectedIndex() == 0) { // Don't delete input database! + System.out.println("Only output databases can be deleted via the GUI!"); + return; + } else { + int indexToDelete = -1; + for (int i = 0; i < dirs.length; i++) { // Cannot use jList.getSelectedIndex() to find dirs as index of + // dirs array is not updated after an element is deleted, unlike + // jList. + // System.out.println(dirs[i].getName() + ", jList selected value " + + // jList.getSelectedValue()); + if (dirs[i].getName().equals(jList.getSelectedValue())) { + indexToDelete = i; + break; + } + } + if ((indexToDelete != -1) && deleteDirectory(dirs[indexToDelete].getAbsoluteFile())) { // Note that dirs + // doesn't + // contain + // "INPUT" as + // first entry, + // unlike model. + model.removeElementAt(jList.getSelectedIndex()); + } else { + throw new FileSystemException( + "Database cannot be deleted; check that the database is not in use! Try again, after closing all connections to the database or restarting the GUI."); + } + } + } catch (IOException e1) { + e1.printStackTrace(); + } + } + + /** + * Force deletion of directory + * + * @param path + * @return boolean + */ + static private boolean deleteDirectory(File path) { + if (path.exists()) { + File[] files = path.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + deleteDirectory(files[i]); + } else { + files[i].delete(); + } + } + } + return (path.delete()); + } + + void jBtnClose_actionPerformed(ActionEvent e) { + dispose(); + } + + void jBtnInit_actionPerformed(ActionEvent e) { + DatabaseUtils.databaseInputUrl = "./input/input"; + DatabaseUtils.inputSchemaUpdateEntityManger(); + try { + new Console().runTool(new String[] { "-url", "jdbc:h2:file:./input/input;AUTO_SERVER=TRUE", "-user", "sa", + "-password", "" }); + } catch (SQLException e1) { + e1.printStackTrace(); + } + } + + private javax.swing.JPanel getMainContentPane() { + if (mainContentPane == null) { + mainContentPane = new javax.swing.JPanel(); + mainContentPane.setLayout(new java.awt.BorderLayout()); + mainContentPane.add(getJPanelProperties(), java.awt.BorderLayout.CENTER); + mainContentPane.add(getJPanelButtons(), java.awt.BorderLayout.NORTH); + } + return mainContentPane; + } + + private javax.swing.JPanel getJPanelButtons() { + if (jPanelButtons == null) { + jPanelButtons = new javax.swing.JPanel(); + jPanelButtons.add(getJBtnInit(), null); + jPanelButtons.add(getJBtnApply(), null); + jPanelButtons.add(getJBtnDelete(), null); + jPanelButtons.add(getJBtnClose(), null); + } + return jPanelButtons; + } + + private javax.swing.JButton getJBtnClose() { + if (jBtnClose == null) { + jBtnClose = new javax.swing.JButton(); + jBtnClose.setText("Close"); + jBtnClose.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnClose_actionPerformed(e); + } + }); + } + return jBtnClose; + } + + private javax.swing.JButton getJBtnDelete() { + if (jBtnDelete == null) { + jBtnDelete = new javax.swing.JButton(); + jBtnDelete.setText("Delete database"); + jBtnDelete.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnDelete_actionPerformed(e); + } + }); + } + return jBtnDelete; + } + + private javax.swing.JButton getJBtnApply() { + if (jBtnApply == null) { + jBtnApply = new javax.swing.JButton(); + jBtnApply.setText("Show database"); + jBtnApply.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnApply_actionPerformed(e); + } + }); + } + return jBtnApply; + } + + private javax.swing.JButton getJBtnInit() { + if (jBtnInit == null) { + jBtnInit = new javax.swing.JButton(); + jBtnInit.setText("Init input database"); + jBtnInit.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnInit_actionPerformed(e); + } + }); + } + return jBtnInit; + } +} diff --git a/src/main/java/microsim/gui/shell/EngineParametersFrame.java b/src/main/java/microsim/gui/shell/EngineParametersFrame.java new file mode 100644 index 00000000..9ee473e3 --- /dev/null +++ b/src/main/java/microsim/gui/shell/EngineParametersFrame.java @@ -0,0 +1,418 @@ +package microsim.gui.shell; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JInternalFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; + +import microsim.engine.EngineListener; +import microsim.engine.SimulationEngine; +import microsim.event.Event; +import microsim.event.EventGroup; +import microsim.event.SystemEventType; + +/** + * Not of interest for users. The frame that controls engine parameters. It is + * shown when the 'Show engine status' menu item of the Control Panel is + * choosen. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class EngineParametersFrame extends JInternalFrame implements EngineListener { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + private SimulationEngine currentEngine; + private long oldSeed; + + ImageIcon imageMiniPreferences = new ImageIcon(getClass().getResource( + "/microsim/gui/icons/engine16.gif")); + + JTabbedPane jTabbedPane = null; + JButton jBtnClose = null; + JPanel jPanelProperties = null; + + JPanel jPanelSeed = null; + JPanel jPanelButtons = null; + JButton jBtnApply = null; + + JButton jBtnCancel = null; + JPanel jPanelEventList = null; + + JLabel jLblEventList = null; + JPanel jPanelLineTime = null; + JPanel jPanelLineSeed = null; + JTextField jTxtSeed = null; + JButton jBtnGenerateSeed = null; + + JTextField jTxtRunNumber = null; + + private javax.swing.JPanel mainContentPane = null; + private javax.swing.JScrollPane jScrollPane = null; + private javax.swing.JTree jTree = null; + + /** + * Constructor. + * + * @param engine + * The simulation engine to edit. + */ + public EngineParametersFrame(SimulationEngine engine) { + currentEngine = engine; + initialize(); + refresh(); + } + + /** Update frame content according to current engine status. */ + public void refresh() { + jTxtSeed.setText("" + currentEngine.getRandomSeed()); + oldSeed = currentEngine.getRandomSeed(); + jTxtRunNumber.setText("" + SimulationEngine.getInstance().getCurrentRunNumber()); + updateEventList(); + currentEngine.addEngineListener(this); + + jBtnApply.setEnabled(false); + + } + + private void initialize() { + // setIconImage(imageMiniPreferences.getImage()); + this.setContentPane(getMainContentPane()); + this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + setSize(new Dimension(450, 338)); + setTitle("JAS-mine engine current status"); + this.setResizable(true); + + } + + private JTabbedPane getJTabbedPane() { + if (jTabbedPane == null) { + jTabbedPane = new JTabbedPane(); + jTabbedPane.add(getJPanelProperties(), "Engine properties"); + jTabbedPane.add(getJPanelEventList(), "Event list"); + } + return jTabbedPane; + } + + private JPanel getJPanelEventList() { + if (jPanelEventList == null) { + jPanelEventList = new JPanel(); + jPanelEventList.setLayout(new java.awt.BorderLayout()); + jPanelEventList + .add(getJLblEventList(), java.awt.BorderLayout.NORTH); + jPanelEventList.add(getJScrollPane(), java.awt.BorderLayout.CENTER); + } + return jPanelEventList; + } + + private JPanel getJPanelProperties() { + if (jPanelProperties == null) { + jPanelProperties = new JPanel(); + jPanelProperties.setLayout(new BorderLayout()); + jPanelProperties.add(getJPanelSeed(), BorderLayout.NORTH); + } + return jPanelProperties; + } + + private JPanel getJPanelSeed() { + if (jPanelSeed == null) { + jPanelSeed = new JPanel(); + GridLayout gl = new GridLayout(); + gl.setColumns(1); + gl.setRows(2); + jPanelSeed.setLayout(gl); + jPanelSeed.setBorder(BorderFactory.createEtchedBorder()); + jPanelSeed.setMinimumSize(new Dimension(317, 70)); + jPanelSeed.setPreferredSize(new Dimension(317, 70)); + jPanelSeed.add(getJPanelLineTime(), null); + jPanelSeed.add(getJPanelLineSeed(), null); + } + return jPanelSeed; + } + + private JPanel getJPanelLineSeed() { + if (jPanelLineSeed == null) { + jPanelLineSeed = new JPanel(); + FlowLayout fl = new FlowLayout(); + fl.setAlignment(FlowLayout.LEFT); + jPanelLineSeed.setLayout(fl); + + jPanelLineSeed.add(new JLabel("Seed random number"), null); + jPanelLineSeed.add(getJTxtSeed(), null); + jPanelLineSeed.add(getJBtnGenerateSeed(), null); + } + return jPanelLineSeed; + } + + private JButton getJBtnGenerateSeed() { + if (jBtnGenerateSeed == null) { + jBtnGenerateSeed = new JButton(); + jBtnGenerateSeed.setText("Generate seed"); + jBtnGenerateSeed + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnGenerateSeed_actionPerformed(e); + } + }); + } + return jBtnGenerateSeed; + } + + private JTextField getJTxtSeed() { + if (jTxtSeed == null) { + jTxtSeed = new JTextField(); + jTxtSeed.setPreferredSize(new Dimension(150, 22)); + jTxtSeed.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jTxtSeed_actionPerformed(e); + } + }); + jTxtSeed.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyTyped(KeyEvent e) { + jTxtSeed_keyTyped(e); + } + }); + } + return jTxtSeed; + } + + private JPanel getJPanelLineTime() { + if (jPanelLineTime == null) { + jPanelLineTime = new JPanel(); + FlowLayout flowLayout = new FlowLayout(); + jPanelLineTime.setLayout(flowLayout); + flowLayout.setAlignment(FlowLayout.LEFT); + + jPanelLineTime.add(Box.createHorizontalStrut(8), null); + jPanelLineTime.add(new JLabel("Run #"), null); + jPanelLineTime.add(getJTxtRunNumber(), null); + jPanelLineTime.add(Box.createHorizontalStrut(8), null); + } + return jPanelLineTime; + } + + private JTextField getJTxtRunNumber() { + if (jTxtRunNumber == null) { + jTxtRunNumber = new JTextField(); + jTxtRunNumber.setPreferredSize(new Dimension(50, 22)); + jTxtRunNumber.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyTyped(KeyEvent e) { + jTxtRunNumber_keyTyped(e); + } + }); + } + return jTxtRunNumber; + } + + void jBtnClose_actionPerformed(ActionEvent e) { + jBtnApply_actionPerformed(e); + close(); + } + + void jBtnGenerateSeed_actionPerformed(ActionEvent e) { + jTxtSeed.setText("" + System.currentTimeMillis()); + jBtnApply.setEnabled(true); + } + + void jBtnApply_actionPerformed(ActionEvent e) { + long newSeed = Long.parseLong(jTxtSeed.getText()); + if (oldSeed != newSeed) + currentEngine.setRandomSeed(newSeed); + + SimulationEngine.getInstance().setCurrentRunNumber(Integer.parseInt(jTxtRunNumber.getText())); + + jBtnApply.setEnabled(false); + } + + void jBtnCancel_actionPerformed(ActionEvent e) { + close(); + } + + private void close() { + currentEngine.removeEngineListener(this); + dispose(); + } + + private void updateEventList() { + currentEngine.pause(); + Event[] eventArray = currentEngine.getEventQueue().getEventArray(); + + DefaultMutableTreeNode root = new DefaultMutableTreeNode(); + + for (int i = 0; i < eventArray.length; i++) { + Event event = eventArray[i]; + DefaultMutableTreeNode folderNode = new DefaultMutableTreeNode( + event); + root.add(folderNode); + if (event instanceof EventGroup) { + Event[] subEvents = ((EventGroup) event).eventsToArray(); + for (int j = 0; j < subEvents.length; j++) { + DefaultMutableTreeNode leafNode = new DefaultMutableTreeNode( + subEvents[j]); + folderNode.add(leafNode); + } + } + } + jTree.setModel(new DefaultTreeModel(root)); + } + + void jTxtSeed_keyTyped(KeyEvent e) { + jBtnApply.setEnabled(true); + } + + void jCmbTimeUnit_actionPerformed(ActionEvent e) { + jBtnApply.setEnabled(true); + } + + void jTxtSeed_actionPerformed(ActionEvent e) { + jBtnApply.setEnabled(true); + } + + void jTxtRunNumber_keyTyped(KeyEvent e) { + jBtnApply.setEnabled(true); + } + + private javax.swing.JPanel getMainContentPane() { + if (mainContentPane == null) { + mainContentPane = new javax.swing.JPanel(); + mainContentPane.setLayout(new java.awt.BorderLayout()); + mainContentPane.add(getJTabbedPane(), java.awt.BorderLayout.CENTER); + mainContentPane + .add(getJPanelButtons(), java.awt.BorderLayout.SOUTH); + } + return mainContentPane; + } + + private javax.swing.JPanel getJPanelButtons() { + if (jPanelButtons == null) { + jPanelButtons = new javax.swing.JPanel(); + jPanelButtons.add(getJBtnApply(), null); + jPanelButtons.add(getJBtnCancel(), null); + jPanelButtons.add(getJBtnClose(), null); + } + return jPanelButtons; + } + + private javax.swing.JButton getJBtnClose() { + if (jBtnClose == null) { + jBtnClose = new javax.swing.JButton(); + jBtnClose.setText("OK"); + jBtnClose.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnClose_actionPerformed(e); + } + }); + } + return jBtnClose; + } + + private javax.swing.JButton getJBtnCancel() { + if (jBtnCancel == null) { + jBtnCancel = new javax.swing.JButton(); + jBtnCancel.setText("Cancel"); + jBtnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnCancel_actionPerformed(e); + } + }); + } + return jBtnCancel; + } + + private javax.swing.JButton getJBtnApply() { + if (jBtnApply == null) { + jBtnApply = new javax.swing.JButton(); + jBtnApply.setText("Apply changes"); + jBtnApply.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnApply_actionPerformed(e); + } + }); + } + return jBtnApply; + } + + private javax.swing.JLabel getJLblEventList() { + if (jLblEventList == null) { + jLblEventList = new javax.swing.JLabel(); + jLblEventList.setBorder(BorderFactory.createEtchedBorder()); + jLblEventList.setText("Current event list"); + } + return jLblEventList; + } + + /** + * This method initializes jScrollPane + * + * @return javax.swing.JScrollPane + */ + private javax.swing.JScrollPane getJScrollPane() { + if (jScrollPane == null) { + jScrollPane = new javax.swing.JScrollPane(); + jScrollPane.setViewportView(getJTree()); + } + return jScrollPane; + } + + /** + * This method initializes jTree + * + * @return javax.swing.JTree + */ + private javax.swing.JTree getJTree() { + if (jTree == null) { + jTree = new javax.swing.JTree(); + jTree.setRootVisible(false); + } + return jTree; + } + + /** Update event list after a step is performed by event list. */ + public void onEngineEvent(SystemEventType event) { + updateEventList(); + } +} diff --git a/src/main/java/microsim/gui/shell/JasConsoleAppender.java b/src/main/java/microsim/gui/shell/JasConsoleAppender.java new file mode 100644 index 00000000..0edaac48 --- /dev/null +++ b/src/main/java/microsim/gui/shell/JasConsoleAppender.java @@ -0,0 +1,126 @@ +package microsim.gui.shell; + +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Serializable; +import java.io.Writer; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.ErrorHandler; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.WriterAppender; + +/** + * JAS custom log4j appender to catch logs and write them into the + * JAS Console window.
+ *
+ * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002-13 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + * + */ +public class JasConsoleAppender implements Appender { + private WriterAppender writer; + + public JasConsoleAppender() { + this.writer = WriterAppender.newBuilder().build(); + } + + public JasConsoleAppender(Layout layout, OutputStream os) { + var osw = new OutputStreamWriter(os); + this.writer = WriterAppender.newBuilder().setLayout(layout).setTarget(osw).build(); + } + + public JasConsoleAppender(Layout layout, Writer writer) { + this.writer = WriterAppender.newBuilder().setLayout(layout).setTarget(writer).build(); + } + + @Override + public void append(LogEvent event) { + this.writer.append(event); + if (MicrosimShell.currentShell != null) + MicrosimShell.currentShell.log(event.getMessage().toString()); + else + System.out.println(event.getMessage().toString()); + } + + @Override + public State getState() { + return this.writer.getState(); + } + + @Override + public void initialize() { + this.writer.initialize(); + } + + @Override + public boolean isStarted() { + return this.writer.isStarted(); + } + + @Override + public boolean isStopped() { + return this.writer.isStopped(); + } + + @Override + public void start() { + this.writer.start(); + } + + @Override + public void stop() { + this.writer.stop(); + } + + @Override + public ErrorHandler getHandler() { + return this.writer.getHandler(); + } + + @Override + public Layout getLayout() { + return this.writer.getLayout(); + } + + @Override + public String getName() { + return this.writer.getName(); + } + + @Override + public boolean ignoreExceptions() { + return this.writer.ignoreExceptions(); + } + + @Override + public void setHandler(ErrorHandler handler) { + this.writer.setHandler(handler); + } + +} diff --git a/src/main/java/microsim/gui/shell/MicrosimShell.java b/src/main/java/microsim/gui/shell/MicrosimShell.java new file mode 100644 index 00000000..2679d189 --- /dev/null +++ b/src/main/java/microsim/gui/shell/MicrosimShell.java @@ -0,0 +1,1568 @@ +package microsim.gui.shell; + +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Toolkit; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; + +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JInternalFrame; +import javax.swing.JOptionPane; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.filechooser.FileSystemView; + +import com.formdev.flatlaf.FlatLightLaf; + +import microsim.engine.EngineListener; +import microsim.engine.SimulationEngine; +import microsim.engine.SimulationManager; +import microsim.event.SystemEventType; +import microsim.exception.SimulationException; +import microsim.gui.GuiUtils; +import microsim.gui.shell.parameter.ParameterFrame; +import microsim.gui.shell.parameter.ParameterInspector; + +/** + * The JAS object is tne main GUI window. It represents the simulation + * environment for the user's simulation models. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + * + */ +public class MicrosimShell extends JFrame { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + public static final double scale = 1.; + + private static String settingsFileName = "jas.ini"; + + /** The frame title. */ + public static String frameTitle = "JAS-mine"; + + private final SimulationController controller = new SimulationController(this); + + private CaptureConsoleWindow consoleWindow = null; + + private javax.swing.JPanel jContentPane = null; + + private javax.swing.JMenuBar jJMenuBar = null; + + private javax.swing.JPanel jPanelTop = null; + + private javax.swing.JToolBar jToolBar = null; + + private javax.swing.JLabel jLabelTime = null; + + private javax.swing.JMenuItem jMenuFileExit = null; + + private javax.swing.JButton jBtnBuild = null; + + private javax.swing.JButton jBtnReload = null; + + private javax.swing.JButton jBtnPlay = null; + + private javax.swing.JButton jBtnStep = null; + + private javax.swing.JButton jBtnPause = null; + + private javax.swing.JButton jBtnUpdateParams = null; + + private javax.swing.JMenu jMenuSimulation = null; + + private javax.swing.JMenu jMenuTools = null; + + private javax.swing.JMenu jMenuHelp = null; + + private javax.swing.JMenuItem jMenuSimulationRestart = null; + + private javax.swing.JMenuItem jMenuSimulationPlay = null; + + private javax.swing.JMenuItem jMenuSimulationStep = null; + + private javax.swing.JMenuItem jMenuSimulationPause = null; + + private javax.swing.JMenuItem jMenuSimulationUpdateParams = null; + + private javax.swing.JMenuItem jMenuSimulationStop = null; + + private javax.swing.JMenuItem jMenuSimulationBuild = null; + + private javax.swing.JMenuItem jMenuSimulationEngine = null; + + private javax.swing.JPanel jPanelTime = null; + + private javax.swing.JLabel jLabelCurTime = null; + + private javax.swing.JPanel jPanelSlider = null; + + private javax.swing.JLabel jLabelSlider = null; + + private javax.swing.JCheckBox jSilentCheck = null; + + private javax.swing.JSlider jSlider = null; + + private javax.swing.JDesktopPane jDesktopPane = null; + + private javax.swing.JSplitPane jSplitPane = null; + + private javax.swing.JLabel jNullLabel = null; + + // private javax.swing.JMenuItem jMenuToolsOption = null; + + private javax.swing.JMenuItem jMenuToolsWindowPositions = null; + + private javax.swing.JMenuItem jMenuToolsDatabaseExplorer = null; + + private javax.swing.JMenuItem jMenuHelpAbout = null; + + // private javax.swing.JMenuItem jMenuHelpWebSite = null; + + private javax.swing.JSplitPane jSplitInternalDesktop = null; // @jve:visual-info + // decl-index=0 + // visual-constraint="756,296" + + public static MicrosimShell currentShell; + + /** + * This is the default full constructor + */ + public MicrosimShell(SimulationEngine engine) { + super(); + + controller.openConfig(); + controller.attachToSimEngine(engine); + + initialize(); + + // engine.setWindowManager(controller); + // controller.refreshMRUMenu(); + setInitButtonStatus(); + + // this.setExtendedState(java.awt.Frame.MAXIMIZED_BOTH); + currentShell = this; + + this.pack(); + this.setSize((int) Toolkit.getDefaultToolkit().getScreenSize().getWidth(), + (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight() - 30); + this.setVisible(true); + jSplitInternalDesktop.setDividerLocation(jSplitInternalDesktop.getHeight() * 4 / 5); + } + + public SimulationController getController() { + return controller; + } + + /** + * This method initializes this + * + * @return void + */ + private void initialize() { + this.setSize(727, 426); // Was left commented out by Michele, but I (Ross) think it's better to have + // this to allow the user to immediately see the parameter boxes at the top half + // of the shell. + this.setContentPane(getJContentPane()); + this.setJMenuBar(getJJMenuBar()); + this.setIconImage(java.awt.Toolkit.getDefaultToolkit().getImage( + getClass().getResource("/microsim/gui/icons/logo_2.png"))); + this.setTitle("JAS-mine"); + + this.setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE); + this.addWindowListener(new java.awt.event.WindowAdapter() { + public void windowClosing(java.awt.event.WindowEvent e) { + controller.quitEngine(); + } + }); + try { + UIManager// .setLookAndFeel("com.jgoodies.looks.plastic.PlasticXPLookAndFeel"); + // .setLookAndFeel("com.jgoodies.looks.windows.WindowsLookAndFeel"); + // .setLookAndFeel("net.infonode.gui.laf.InfoNodeLookAndFeel"); + .setLookAndFeel(new FlatLightLaf()); + SwingUtilities.updateComponentTreeUI(this); + } catch (Exception e) { + System.out.println("Error loading L&F " + e); + } + + consoleWindow = new CaptureConsoleWindow(); + jSplitInternalDesktop.setBottomComponent(consoleWindow); + consoleWindow.show(); + } + + public void attachToSimEngine(SimulationEngine engine) { + controller.attachToSimEngine(engine); + } + + /** + * This method initializes jContentPane + * + * @return javax.swing.JPanel + */ + public javax.swing.JPanel getJContentPane() { + if (jContentPane == null) { + jContentPane = new javax.swing.JPanel(); + jContentPane.setLayout(new java.awt.BorderLayout()); + jContentPane.add(getJPanelTop(), java.awt.BorderLayout.NORTH); + jContentPane.add(getJSplitPane(), java.awt.BorderLayout.CENTER); + } + return jContentPane; + } + + /** + * This method initializes jJMenuBar + * + * @return javax.swing.JMenuBar + */ + private javax.swing.JMenuBar getJJMenuBar() { + if (jJMenuBar == null) { + jJMenuBar = new javax.swing.JMenuBar(); + // jJMenuBar.add(getJMenuFile()); + jJMenuBar.add(getJMenuSimulation()); + jJMenuBar.add(getJMenuTools()); + jJMenuBar.add(getJMenuHelp()); + } + return jJMenuBar; + } + + /** + * This method initializes jPanelTop + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getJPanelTop() { + if (jPanelTop == null) { + jPanelTop = new javax.swing.JPanel(); + jPanelTop.setLayout(new java.awt.BorderLayout()); + jPanelTop.add(getJToolBar(), java.awt.BorderLayout.NORTH); + jPanelTop.add(getJPanelTime(), java.awt.BorderLayout.CENTER); + jPanelTop + .setBorder(javax.swing.BorderFactory + .createEtchedBorder(javax.swing.border.EtchedBorder.RAISED)); + } + return jPanelTop; + } + + /** + * This method initializes jToolBar + * + * @return javax.swing.JToolBar + */ + private javax.swing.JToolBar getJToolBar() { + if (jToolBar == null) { + jToolBar = new javax.swing.JToolBar(); + // jToolBar.add(getJBtnLoad()); + jToolBar.addSeparator(); + jToolBar.add(getJBtnReload()); + jToolBar.addSeparator(); + jToolBar.add(getJBtnBuild()); + jToolBar.addSeparator(); + jToolBar.addSeparator(); + jToolBar.add(getJBtnPlay()); + jToolBar.add(getJBtnStep()); + // jToolBar.add(getJBtnTimeStep()); + jToolBar.add(getJBtnPause()); + jToolBar.add(getJBtnUpdateParameters()); + jToolBar.addSeparator(); + // jToolBar.add(getJSilentCheck()); //Ross: This has been removed in order to + // avoid misuse by inexperienced users, who might try to import/export to the + // database despite switching the connection off. Now all JAS-mine models + // launched from the GUI will automatically have the database connection + // created. If the user wants to turn this connection off, they can do so + // programmatically by setting turnOffDatabaseConnection to true in the Start + // class template of the simulation project created by the JAS-mine plugin for + // Eclipse IDE. + // jToolBar.addSeparator(); + jToolBar.addSeparator(new Dimension(50, 30)); + jToolBar.add(getJPanelSlider()); + jToolBar.setPreferredSize(new java.awt.Dimension(414, 40)); + } + return jToolBar; + } + + /** + * This method initializes jLabelTime + * + * @return javax.swing.JLabel + */ + private javax.swing.JLabel getJLabelTime() { + if (jLabelTime == null) { + jLabelTime = new javax.swing.JLabel(); + jLabelTime.setFont(new Font(jLabelTime.getFont().getFontName(), jLabelTime.getFont().getStyle(), + (int) (scale * jLabelTime.getFont().getSize()))); + } + return jLabelTime; + } + + /** + * This method initializes jMenuFileExit + * + * @return javax.swing.JMenuItem + */ + private javax.swing.JMenuItem getJMenuFileExit() { + if (jMenuFileExit == null) { + jMenuFileExit = new javax.swing.JMenuItem(); + jMenuFileExit.setText("Quit"); + jMenuFileExit.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/quit.gif"))); + jMenuFileExit + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.quitEngine(); + } + }); + } + return jMenuFileExit; + } + + /** + * This method initializes jBtnBuild + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getJBtnBuild() { + if (jBtnBuild == null) { + jBtnBuild = new javax.swing.JButton(); + jBtnBuild.setIcon(new javax.swing.ImageIcon(getClass().getResource( + "/microsim/gui/icons/simulation_build.gif"))); + jBtnBuild.setToolTipText("Build simulation model"); + jBtnBuild.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.buildModel(); + } + }); + } + return jBtnBuild; + } + + public void setTimeLabel(String newTime) { + jLabelTime.setText(newTime); + } + + /** Enable and disable the simulation buttons as the initial state. */ + public void setInitButtonStatus() { + jBtnPlay.setEnabled(false); + jMenuSimulationPlay.setEnabled(false); + jBtnStep.setEnabled(false); + jMenuSimulationStep.setEnabled(false); + jMenuSimulationStop.setEnabled(false); + jMenuSimulationPause.setEnabled(false); + jMenuSimulationUpdateParams.setEnabled(false); + jBtnPause.setEnabled(false); + jBtnUpdateParams.setEnabled(false); + jBtnBuild.setEnabled(true); + jMenuSimulationBuild.setEnabled(true); + jMenuSimulationRestart.setEnabled(false); + jMenuSimulation.revalidate(); + jMenuSimulation.repaint(); + + getContentPane().setCursor( + Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + /** Enable and disable the simulation buttons according to the built state. */ + public void setBuiltButtonStatus() { + jBtnPlay.setEnabled(true); + jMenuSimulationPlay.setEnabled(true); + jBtnStep.setEnabled(true); + jMenuSimulationStep.setEnabled(true); + jMenuSimulationStop.setEnabled(true); + jBtnBuild.setEnabled(false); + jMenuSimulationPause.setEnabled(true); + jMenuSimulationUpdateParams.setEnabled(true); + jBtnPause.setEnabled(true); + jBtnUpdateParams.setEnabled(true); + jMenuSimulationBuild.setEnabled(false); + jMenuSimulationRestart.setEnabled(true); + jMenuSimulation.revalidate(); + jMenuSimulation.repaint(); + } + + public static class RootViews extends FileSystemView { + + public File[] getRoots() { + File[] oldRoots = super.getRoots(); + File[] roots = new File[2 + oldRoots.length]; + // roots[0] = new File(Sim.jasProjectsPath); + // roots[1] = new File(Sim.getStartDirectory()); + System.arraycopy(oldRoots, 0, roots, 2, oldRoots.length); + + return roots; + } + + /* + * (non-Javadoc) + * + * @see javax.swing.filechooser.FileSystemView#createNewFolder(java.io.File) + */ + public File createNewFolder(File arg0) throws IOException { + String newFolder = JOptionPane.showInputDialog(null, + "Type the name of the new folder"); + if (newFolder == null) + return null; + + File newFile = new File(arg0, newFolder); + + if (newFile.mkdir()) + return newFile; + else + return null; + } + } + + public class SimulationController implements EngineListener { + + private SimulationEngine callerEngine; + + // private HashMap windowBag; + + private Properties settings; + + JFileChooser jfc; + + private MicrosimShell jasWindow; + + private List parameterFrames = new ArrayList(); + + public SimulationController(MicrosimShell owner) { + jasWindow = owner; + // windowBag = new HashMap(); + } + + private void openConfig() { + settings = new Properties(); + settings.setProperty("LookAndFeel", ""); + + settings.setProperty("ProjectsPath", ""); + settings.setProperty("EditorPath", ""); + + try { + settings.load(new java.io.FileInputStream(settingsFileName)); + } catch (java.io.IOException ioe) { + } + + if (!settings.getProperty("ProjectsPath").equals("")) { + File fl = new File(settings.getProperty("ProjectsPath")); + if (!fl.exists()) + JOptionPane + .showMessageDialog( + jasWindow, + "The JAS-mine projects path does no more exist on this file system.\n " + + "Please check it from the Tool\\JAS Options menu"); + else + // Sim.jasProjectsPath = settings.getProperty("ProjectsPath"); + ; + } + + // if (!settings.getProperty("EditorPath").equals("")) + // Sim.setEditorPath(settings.getProperty("EditorPath")); + } + + public void showProperties() { + boolean st = callerEngine.getRunningStatus(); + callerEngine.pause(); + + EngineParametersFrame paramFrame = new EngineParametersFrame(callerEngine); + getJDesktopPane().add(paramFrame); + paramFrame.show(); + callerEngine.setRunningStatus(st); + } + + public void setTurnOffDatabaseConnection(boolean turnOffDatabaseConnection) { + callerEngine.setTurnOffDatabaseConnection(turnOffDatabaseConnection); + } + + public boolean isTurnOffDatabaseConnection() { + return callerEngine.isTurnOffDatabaseConnection(); + } + + public void showDatabaseExplorer() { + boolean st = callerEngine.getRunningStatus(); + callerEngine.pause(); + + DatabaseExplorerFrame dbFrame = new DatabaseExplorerFrame(callerEngine); + getJDesktopPane().add(dbFrame); + dbFrame.show(); + callerEngine.setRunningStatus(st); + } + + public void editProperties() { + // (new JASParameters(jasWindow, settings)).setVisible(true); + } + + public void startModel() { + callerEngine.startSimulation(); + } + + public void pauseModel() { + callerEngine.pause(); + } + + public void updateModelParams() { + for (ParameterFrame parameterFrame : parameterFrames) { + parameterFrame.save(); + } + } + + public void stopModel() { + callerEngine.performAction(SystemEventType.Stop); + } + + public void restartModel() { + callerEngine.pause(); + + closeCurrentModels(); + callerEngine.rebuildModels(); + + // attachToSimEngine(callerEngine); + + setInitButtonStatus(); + } + + // public void showProperties() { + // boolean st = callerEngine.getRunningStatus(); + // callerEngine.stop(); + // + // EngineParametersFrame paramFrame = new EngineParametersFrame( + // callerEngine, jasWindow); + // getJDesktopPane().add(paramFrame); + // paramFrame.show(); + // callerEngine.setRunning(st); + // } + // + // public void changeEventTimeTreshold(int value) { + // callerEngine.getEventList().setEventTimeTreshold(value); + // } + + public void attachToSimEngine(SimulationEngine engine) { + callerEngine = engine; + callerEngine.addEngineListener(this); + } + + public void doStep() throws SimulationException { + callerEngine.pause(); + callerEngine.step(); + } + + public void changeEventTimeTreshold(int value) { + callerEngine.setEventTimeTreshold(value); + } + + // public void showAPI(String relativeDir, String fileName) { + // String path = Sim.getStartDirectory() + relativeDir; + // + // String command; + // if (System.getProperty("file.separator").equals("/")) { + // command = "netscape " + path + "/" + fileName; + // } else + // command = "cmd /C start /D\"" + path.replace('/', '\\') + "\" " + fileName; + // + // try { + // Runtime.getRuntime().exec(command); + // } catch (Exception ex) { + // } + // } + + public void navigateWebSite() { + // String command, path = "http://jaslibrary.sourceforge.net"; + String command, path = "http://www.jas-mine.net"; + if (System.getProperty("file.separator").equals("/")) + command = "netscape " + path; + else + command = "cmd /C start " + path; + + try { + Runtime.getRuntime().exec(command); + } catch (Exception ex) { + } + } + + private void quitEngine() { + callerEngine.pause(); + + callerEngine.quit(); + + } + + public void closeCurrentModels() { + try { + JInternalFrame[] frames = getJDesktopPane().getAllFrames(); + for (int i = 0; i < frames.length; i++) + if (frames[i] != consoleWindow) + frames[i].dispose(); + } catch (Exception ex) { + } + + setTimeLabel(""); + } + + /** Ask engine to build currently loaded models. */ + public void buildModel() { + getContentPane().setCursor( + Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + callerEngine.buildModels(); + setBuiltButtonStatus(); + + getContentPane().setCursor( + Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + /** + * Return a reference to the settings list. + * + * @return An instance of the Java standard Properties class. + */ + public Properties getSettings() { + return settings; + } + + /* + * (non-Javadoc) + * + * @see jas.engine.IWindowManager#disposeSimWindows() + */ + // public void disposeSimWindows() { + // Iterator it = windowBag.values().iterator(); + // while (it.hasNext()) + // ((SimulationWindow) it.next()).setWindow(null); + // + // // Remove all frames but output window + // JInternalFrame[] frames = jDesktopPane.getAllFrames(); + // int i = frames.length; + // while (--i >= 0) + // if (frames[i] != consoleWindow) + // jDesktopPane.remove(frames[i]); + // jDesktopPane.repaint(); + // + // try { + // Frame[] frmes = MicrosimShell.getFrames(); + // for (i = 0; i < frmes.length; i++) + // if (frmes[i] != jasWindow) + // frmes[i].dispose(); + // } catch (Exception ex) { + // } + // + // // windowBag.clear(); + // } + + public void printWindowPositions() { + for (JInternalFrame jInternalFrame : jDesktopPane.getAllFrames()) { + System.out.println(jInternalFrame.getTitle() + " [X,Y,W,H] " + jInternalFrame.getX() + ", " + + jInternalFrame.getY() + ", " + jInternalFrame.getWidth() + ", " + jInternalFrame.getHeight()); + } + } + /* + * (non-Javadoc) + * + * @see jas.engine.IWindowManager#addSimWindow(jas.engine.ISimModel, + * javax.swing.JFrame) + */ + // public void addSimWindow(SimulationManager owner, Frame window) { + // if (window instanceof JFrame) + // addSimWindow(owner, buildInternalFrame((JFrame) window)); + // else { + // SimulationWindow win = (SimulationWindow) windowBag.get(window.getTitle()); + // if (win == null) { + // String modelId = (owner == null ? "" : owner.getClass().getCanonicalName()); + // win = new SimulationWindow(modelId, window.getTitle(), window); + // windowBag.put(win.getKey(), win); + // win.setDefaultPosition(window.getBounds()); + // } else + // win.setWindow(window); + // + // Rectangle r = win.getDefaultPosition(); + // window.setBounds(r.x, r.y, r.width, r.height); + // window.setVisible(true); + // } + // } + + // private JInternalFrame buildInternalFrame(JFrame frame) { + // JInternalFrame intF = new JInternalFrame(frame.getTitle(), frame + // .isResizable(), false, frame.isResizable(), true); + // + // if (frame.getJMenuBar() != null) + // intF.setJMenuBar(frame.getJMenuBar()); + // + // WindowGrabber wg = new WindowGrabber(intF); + // frame.addWindowListener(wg); + // + // intF.getContentPane().add(frame.getContentPane()); + // if (frame.getIconImage() != null) + // intF.setFrameIcon(new ImageIcon(frame.getIconImage())); + // intF.setSize(frame.getSize()); + // return intF; + // } + + /* + * (non-Javadoc) + * + * @see jas.engine.IWindowManager#addSimWindow(jas.engine.ISimModel, + * javax.swing.JInternalFrame) + */ + // public void addSimWindow(SimulationManager owner, JInternalFrame window) { + // SimulationWindow win = (SimulationWindow) windowBag.get(window.getTitle()); + // if (win == null) { + // String modelId = (owner == null ? "" : owner.getClass().getCanonicalName()); + // win = new SimulationWindow(modelId, window.getTitle(), window); + // windowBag.put(window.getTitle(), win); + // win.setDefaultPosition(window.getBounds()); + // } else + // win.setWindow(window); + // + // jasWindow.getJDesktopPane().add(window); + // Rectangle r = win.getDefaultPosition(); + // window.reshape(r.x, r.y, r.width, r.height); + // window.show(); + // } + + /* + * (non-Javadoc) + * + * @see jas.engine.IWindowManager#getSimWindows() + */ + // public SimulationWindow[] getSimWindows() { + // Iterator it; + // int i; + // + // SimulationWindow[] wins = new SimulationWindow[windowBag.values().size()]; + // for (i = 0, it = windowBag.values().iterator(); it.hasNext(); i++) + // wins[i] = (SimulationWindow) it.next(); + // + // return wins; + // } + // + // /* + // * (non-Javadoc) + // * + // * @see jas.engine.IWindowManager#addSimWindow(jas.engine.SimWindow) + // */ + // public void addSimWindow(SimulationWindow window) { + // windowBag.put(window.getKey(), window); + // } + + public void onEngineEvent(SystemEventType event) { + if (event.equals(SystemEventType.Step)) + getJLabelTime().setText("" + callerEngine.getEventQueue().getTime()); + else if (event.equals(SystemEventType.Setup)) { + parameterFrames.clear(); + for (SimulationManager model : controller.callerEngine.getModelArray()) { + List fields = ParameterInspector.extractModelParameters(model.getClass()); + + // Check that getter and setter exists for each model parameter (to ensure + // ability to control via microsim.shell GUI) + HashSet getters = new HashSet(); + HashSet setters = new HashSet(); + Method[] methods = model.getClass().getMethods(); + + for (Method method : methods) { + if (isGetter(method)) { + getters.add(method.getName()); + } else if (isSetter(method)) { + setters.add(method.getName()); + } + } + + for (Field modelParameter : fields) { + String modelParameterName = modelParameter.getName(); + if (modelParameterName.length() > 1) { + if (Character.isLowerCase(modelParameterName.charAt(0)) + && Character.isUpperCase(modelParameterName.charAt(1))) { + if (!getters.contains("get" + modelParameterName)) { // handles cases for fields with a + // name whose first character is + // lower case, followed by a + // capital letter, e.g. nWorkers. + // In this case, the Java Beans + // convention is for a getter + // called getnWorkers, instead of + // getNWorkers. + if (!getters.contains("is" + modelParameterName)) { // handles case for boolean 'is' + // getter methods + throw new RuntimeException("Model parameter " + modelParameterName + + " has no getter method. Please create a getter method called get" + + modelParameterName + " in the " + model.getClass() + + " to enable this model parameter to be read by the GUI."); + } + } + if (!setters.contains("set" + modelParameterName)) { + throw new RuntimeException("Model parameter " + modelParameterName + + " has no setter method. Please create a setter method called set" + + modelParameterName + " in the " + model.getClass() + + " to enable this model parameter to be controlled via the GUI."); + } + } else { + String capModelParameterName = modelParameterName.substring(0, 1).toUpperCase() + + modelParameterName.substring(1, modelParameterName.length()); // Ensure first + // letter of + // name is + // capitalised + String getterName = "get" + capModelParameterName; + String setterName = "set" + capModelParameterName; + + if (!getters.contains(getterName)) { + if (!getters.contains("is" + capModelParameterName)) { // handles case for boolean + // 'is' getter methods + throw new RuntimeException("Model parameter " + modelParameterName + + " has no getter method. Please create a getter method called " + + getterName + " in the " + model.getClass() + + " to enable this model parameter to be read by the GUI."); + } + } + if (!setters.contains(setterName)) { + throw new RuntimeException("Model parameter " + modelParameterName + + " has no setter method. Please create a setter method called " + + setterName + " in the " + model.getClass() + + " to enable this model parameter to be controlled via the GUI."); + } + } + } else { // Still need to check that getter/setters exist for case where a single + // character is used for the model parameter name. + String capModelParameterName = modelParameterName.substring(0, 1).toUpperCase() + + modelParameterName.substring(1, modelParameterName.length()); // Ensure first + // letter of name is + // capitalised + String getterName = "get" + capModelParameterName; + String setterName = "set" + capModelParameterName; + + if (!getters.contains(getterName)) { + if (!getters.contains("is" + capModelParameterName)) { // handles case for boolean 'is' + // getter methods + throw new RuntimeException("Model parameter " + modelParameterName + + " has no getter method. Please create a getter method called " + + getterName + " in the " + model.getClass() + + " to enable this model parameter to be read by the GUI."); + } + } + if (!setters.contains(setterName)) { + throw new RuntimeException("Model parameter " + modelParameterName + + " has no setter method. Please create a setter method called " + setterName + + " in the " + model.getClass() + + " to enable this model parameter to be controlled via the GUI."); + } + } + } + + if (fields.size() > 0) { + ParameterFrame parameterFrame = new ParameterFrame(model); + parameterFrame.setResizable(false); // Now in scrollpane, cannot resize anyway, so set to false. + GuiUtils.addWindow(parameterFrame); + // GuiUtils.addWindow(parameterFrame); + parameterFrames.add(parameterFrame); + } + } + } else if (event.equals(SystemEventType.Build)) { + for (ParameterFrame parameterFrame : parameterFrames) { + parameterFrame.save(); + } + } + } + + } + + private static boolean isGetter(Method method) { + if (!(method.getName().startsWith("get") || method.getName().startsWith("is"))) + return false; + if (method.getParameterTypes().length != 0) + return false; + if (void.class.equals(method.getReturnType())) + return false; + return true; + } + + private static boolean isSetter(Method method) { + if (!method.getName().startsWith("set")) + return false; + if (method.getParameterTypes().length != 1) + return false; + return true; + } + + /** + * This method initializes jBtnReload + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getJBtnReload() { + if (jBtnReload == null) { + jBtnReload = new javax.swing.JButton(); + jBtnReload.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/simulation_refresh.gif"))); + // .getResource("/icons/simulation_refresh.gif"))); + jBtnReload.setToolTipText("Restart simulation model"); + jBtnReload.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.restartModel(); + } + }); + } + return jBtnReload; + } + + /** + * This method initializes jBtnPlay + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getJBtnPlay() { + if (jBtnPlay == null) { + jBtnPlay = new javax.swing.JButton(); + jBtnPlay.setIcon(new javax.swing.ImageIcon(getClass().getResource( + "/microsim/gui/icons/simulation_play.gif"))); + jBtnPlay.setToolTipText("Start simulation"); + jBtnPlay.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.startModel(); + } + }); + } + return jBtnPlay; + } + + /** + * This method initializes jBtnStep + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getJBtnStep() { + if (jBtnStep == null) { + jBtnStep = new javax.swing.JButton(); + jBtnStep.setIcon(new javax.swing.ImageIcon(getClass().getResource( + "/microsim/gui/icons/simulation_step.gif"))); + jBtnStep.setToolTipText("Execute next scheduled action"); + jBtnStep.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + try { + controller.doStep(); + } catch (SimulationException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + }); + } + return jBtnStep; + } + + /** + * This method initializes jBtnPause + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getJBtnPause() { + if (jBtnPause == null) { + jBtnPause = new javax.swing.JButton(); + jBtnPause.setIcon(new javax.swing.ImageIcon(getClass().getResource( + "/microsim/gui/icons/simulation_pause.gif"))); + jBtnPause.setToolTipText("Pause simulation"); + jBtnPause.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.pauseModel(); + } + }); + } + return jBtnPause; + } + + /** + * This method initializes jBtnPause + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getJBtnUpdateParameters() { + if (jBtnUpdateParams == null) { + jBtnUpdateParams = new javax.swing.JButton(); + jBtnUpdateParams.setIcon(new javax.swing.ImageIcon(getClass().getResource( + "/microsim/gui/icons/simulation_update_params.png"))); + jBtnUpdateParams.setToolTipText("Update parameters in the live simulation"); + jBtnUpdateParams.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.updateModelParams(); + } + }); + } + return jBtnUpdateParams; + } + + /** + * This method initializes jMenuSimulation + * + * @return javax.swing.JMenu + */ + private javax.swing.JMenu getJMenuSimulation() { + if (jMenuSimulation == null) { + jMenuSimulation = new javax.swing.JMenu(); + jMenuSimulation.add(getJMenuSimulationBuild()); + jMenuSimulation.add(getJMenuSimulationRestart()); + jMenuSimulation.addSeparator(); + jMenuSimulation.add(getJMenuSimulationPlay()); + jMenuSimulation.add(getJMenuSimulationStep()); + // jMenuSimulation.add(getJMenuSimulationTimeStep()); + jMenuSimulation.add(getJMenuSimulationPause()); + jMenuSimulation.add(getJMenuSimulationUpdateParams()); + jMenuSimulation.add(getJMenuSimulationStop()); + jMenuSimulation.addSeparator(); + jMenuSimulation.add(getJMenuSimulationEngine()); + jMenuSimulation.setText("Simulation"); + jMenuSimulation.setFont(new Font(jMenuSimulation.getFont().getFontName(), + jMenuSimulation.getFont().getStyle(), (int) (scale * jMenuSimulation.getFont().getSize()))); + // jMenuSimulation.addSeparator(); + jMenuSimulation.add(getJMenuFileExit()); + } + return jMenuSimulation; + } + + /** + * This method initializes jMenuSimulationEngine + * + * @return javax.swing.JMenuItem + */ + private javax.swing.JMenuItem getJMenuSimulationEngine() { + if (jMenuSimulationEngine == null) { + jMenuSimulationEngine = new javax.swing.JMenuItem(); + jMenuSimulationEngine.setText("Show engine status"); + jMenuSimulationEngine + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.showProperties(); + } + }); + } + return jMenuSimulationEngine; + } + + /** + * This method initializes jMenuTools + * + * @return javax.swing.JMenu + */ + private javax.swing.JMenu getJMenuTools() { + if (jMenuTools == null) { + jMenuTools = new javax.swing.JMenu(); + jMenuTools.setText("Tools"); + jMenuTools.setFont(new Font(jMenuTools.getFont().getFontName(), jMenuTools.getFont().getStyle(), + (int) (scale * jMenuTools.getFont().getSize()))); + // jMenuTools.add(getJMenuToolsParameter()); + // jMenuTools.add(getJMenuToolsGraph()); + // jMenuTools.add(getJMenuToolsDB()); + // jMenuTools.addSeparator(); + // jMenuTools.add(getJMenuToolsOption()); + jMenuTools.add(getJMenuToolsWindowPositions()); + jMenuTools.add(getJMenuToolsDatabaseExplorer()); + } + return jMenuTools; + } + + /** + * This method initializes jMenuHelp + * + * @return javax.swing.JMenu + */ + private javax.swing.JMenu getJMenuHelp() { + if (jMenuHelp == null) { + jMenuHelp = new javax.swing.JMenu(); + // jMenuHelp.add(getJMenuHelpGuide()); + // jMenuHelp.addSeparator(); + // jMenuHelp.add(getJMenuHelpAPI()); + // jMenuHelp.add(getJMenuHelpLibraries()); + // jMenuHelp.add(getJMenuHelpWebSite()); + // jMenuHelp.addSeparator(); + jMenuHelp.add(getJMenuHelpAbout()); + jMenuHelp.setText("Help"); + jMenuHelp.setFont(new Font(jMenuHelp.getFont().getFontName(), jMenuHelp.getFont().getStyle(), + (int) (scale * jMenuHelp.getFont().getSize()))); + } + return jMenuHelp; + } + + /** + * This method initializes jMenuSimulationRestart + * + * @return javax.swing.JMenuItem + */ + private javax.swing.JMenuItem getJMenuSimulationRestart() { + if (jMenuSimulationRestart == null) { + jMenuSimulationRestart = new javax.swing.JMenuItem(); + jMenuSimulationRestart.setText("Restart simulation"); + jMenuSimulationRestart.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/simulation_refresh.gif"))); + jMenuSimulationRestart + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.restartModel(); + } + }); + } + return jMenuSimulationRestart; + } + + /** + * This method initializes jMenuSimulationPlay + * + * @return javax.swing.JMenuItem + */ + private javax.swing.JMenuItem getJMenuSimulationPlay() { + if (jMenuSimulationPlay == null) { + jMenuSimulationPlay = new javax.swing.JMenuItem(); + jMenuSimulationPlay.setText("Play"); + jMenuSimulationPlay.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/simulation_play.gif"))); + jMenuSimulationPlay + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.startModel(); + } + }); + } + return jMenuSimulationPlay; + } + + /** + * This method initializes jMenuSimulationStep + * + * @return javax.swing.JMenuItem + */ + private javax.swing.JMenuItem getJMenuSimulationStep() { + if (jMenuSimulationStep == null) { + jMenuSimulationStep = new javax.swing.JMenuItem(); + jMenuSimulationStep.setText("Step"); + jMenuSimulationStep.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/simulation_step.gif"))); + jMenuSimulationStep + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + try { + controller.doStep(); + } catch (SimulationException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + }); + } + return jMenuSimulationStep; + } + + /** + * This method initializes jMenuSimulationPause + * + * @return javax.swing.JMenuItem + */ + private javax.swing.JMenuItem getJMenuSimulationPause() { + if (jMenuSimulationPause == null) { + jMenuSimulationPause = new javax.swing.JMenuItem(); + jMenuSimulationPause.setText("Pause"); + jMenuSimulationPause.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/simulation_pause.gif"))); + jMenuSimulationPause + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.pauseModel(); + } + }); + } + return jMenuSimulationPause; + } + + /** + * This method initializes jMenuSimulationPause + * + * @return javax.swing.JMenuItem + */ + private javax.swing.JMenuItem getJMenuSimulationUpdateParams() { + if (jMenuSimulationUpdateParams == null) { + jMenuSimulationUpdateParams = new javax.swing.JMenuItem(); + jMenuSimulationUpdateParams.setText("Update Parameters"); + jMenuSimulationUpdateParams.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/simulation_update_params.png"))); + jMenuSimulationUpdateParams + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.updateModelParams(); + } + }); + } + return jMenuSimulationUpdateParams; + } + + /** + * This method initializes jMenuSimulationStop + * + * @return javax.swing.JMenuItem + */ + private javax.swing.JMenuItem getJMenuSimulationStop() { + if (jMenuSimulationStop == null) { + jMenuSimulationStop = new javax.swing.JMenuItem(); + jMenuSimulationStop.setText("Stop"); + jMenuSimulationStop.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/simulation_stop.gif"))); + jMenuSimulationStop + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.stopModel(); + } + }); + } + return jMenuSimulationStop; + } + + private javax.swing.JMenuItem getJMenuToolsWindowPositions() { + if (jMenuToolsWindowPositions == null) { + jMenuToolsWindowPositions = new javax.swing.JMenuItem(); + jMenuToolsWindowPositions.setText("Print window positions"); + jMenuToolsWindowPositions.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/console.gif"))); + jMenuToolsWindowPositions + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.printWindowPositions(); + } + }); + } + return jMenuToolsWindowPositions; + } + + private javax.swing.JCheckBox getJSilentCheck() { + if (jSilentCheck == null) { + jSilentCheck = new javax.swing.JCheckBox(); + jSilentCheck.setSelected(controller.isTurnOffDatabaseConnection()); + jSilentCheck.setText("Turn off database"); + jSilentCheck.setFont(new Font(jSilentCheck.getFont().getFontName(), jSilentCheck.getFont().getStyle(), + (int) (scale * jSilentCheck.getFont().getSize()))); + jSilentCheck + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.setTurnOffDatabaseConnection(jSilentCheck.isSelected()); + jSilentCheck.setSelected(controller.isTurnOffDatabaseConnection()); + } + }); + } + return jSilentCheck; + } + + private javax.swing.JMenuItem getJMenuToolsDatabaseExplorer() { + if (jMenuToolsDatabaseExplorer == null) { + jMenuToolsDatabaseExplorer = new javax.swing.JMenuItem(); + jMenuToolsDatabaseExplorer.setText("Database explorer"); + jMenuToolsDatabaseExplorer.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/console.gif"))); + jMenuToolsDatabaseExplorer + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.showDatabaseExplorer(); + } + }); + } + return jMenuToolsDatabaseExplorer; + } + + /** + * This method initializes jMenuSimulationBuild + * + * @return javax.swing.JMenuItem + */ + private javax.swing.JMenuItem getJMenuSimulationBuild() { + if (jMenuSimulationBuild == null) { + jMenuSimulationBuild = new javax.swing.JMenuItem(); + jMenuSimulationBuild.setText("Build model"); + jMenuSimulationBuild.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/simulation_build.gif"))); + jMenuSimulationBuild + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + controller.buildModel(); + } + }); + } + return jMenuSimulationBuild; + } + + /** + * This method initializes jPanelTime + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getJPanelTime() { + if (jPanelTime == null) { + jPanelTime = new javax.swing.JPanel(); + jPanelTime.setLayout(new java.awt.BorderLayout()); + jPanelTime.add(getJLabelCurTime(), java.awt.BorderLayout.WEST); + jPanelTime.add(getJLabelTime(), java.awt.BorderLayout.CENTER); + } + return jPanelTime; + } + + /** + * This method initializes jLabelCurTime + * + * @return javax.swing.JLabel + */ + private javax.swing.JLabel getJLabelCurTime() { + if (jLabelCurTime == null) { + jLabelCurTime = new javax.swing.JLabel(); + jLabelCurTime.setText("Current time:"); + jLabelCurTime.setFont(new java.awt.Font("Franklin Gothic Medium", + java.awt.Font.ITALIC, (int) (scale * 12))); + } + return jLabelCurTime; + } + + /** + * This method initializes jPanelSlider + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getJPanelSlider() { + if (jPanelSlider == null) { + jPanelSlider = new javax.swing.JPanel(); + jPanelSlider.setLayout(null); + jPanelSlider.add(getJLabelSlider(), null); + jPanelSlider.add(getJSlider(), null); + + jPanelSlider.setSize(100, 30); + jPanelSlider.setPreferredSize(new java.awt.Dimension(100, 30)); + } + return jPanelSlider; + } + + /** + * This method initializes jLabelSlider + * + * @return javax.swing.JLabel + */ + private javax.swing.JLabel getJLabelSlider() { + if (jLabelSlider == null) { + jLabelSlider = new javax.swing.JLabel(); + jLabelSlider.setBounds(0, 0, (int) (scale * 173), 18); + jLabelSlider.setText(" Simulation speed: max"); + jLabelSlider.setFont(new java.awt.Font("Franklin Gothic Medium", + java.awt.Font.PLAIN, (int) (scale * 12))); + jLabelSlider.setName("jLabelSlider"); + } + return jLabelSlider; + } + + /** + * This method initializes jSlider + * + * @return javax.swing.JSlider + */ + private javax.swing.JSlider getJSlider() { + if (jSlider == null) { + jSlider = new javax.swing.JSlider(); + jSlider.setBounds(0, 18, (int) (scale * 173), 18); + jSlider.setName("jSlider"); + jSlider.setMaximum(200); + jSlider.setValue(0); + jSlider.setInverted(true); + jSlider.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent e) { + int value = jSlider.getValue(); + if (value == jSlider.getMinimum()) + jLabelSlider.setText(" Simulation speed: max"); + else + jLabelSlider.setText(" Simulation speed: " + + (200 - value)); + + controller.changeEventTimeTreshold(value); + } + }); + } + return jSlider; + } + + /** + * This method initializes jDesktopPane + * + * @return javax.swing.JDesktopPane + */ + public javax.swing.JDesktopPane getJDesktopPane() { + if (jDesktopPane == null) { + jDesktopPane = new javax.swing.JDesktopPane(); + // jDesktopPane.setLayout(new GridLayout(2, 2)); + } + return jDesktopPane; + } + + /** + * This method initializes jSplitPane + * + * @return javax.swing.JSplitPane + */ + private javax.swing.JSplitPane getJSplitPane() { + if (jSplitPane == null) { + jSplitPane = new javax.swing.JSplitPane(); + jSplitPane.setRightComponent(getJSplitInternalDesktop()); + jSplitPane.setLeftComponent(getJNullLabel()); + jSplitPane.setOneTouchExpandable(true); + } + return jSplitPane; + } + + /** + * This method initializes jNullLabel + * + * @return javax.swing.JLabel + */ + private javax.swing.JLabel getJNullLabel() { + if (jNullLabel == null) { + jNullLabel = new javax.swing.JLabel(); + } + return jNullLabel; + } + + // private class WindowGrabber extends WindowAdapter { + // private JInternalFrame frame; + // + // public WindowGrabber(JInternalFrame internalFrame) { + // frame = internalFrame; + // } + // + // public void windowActivated(WindowEvent e) { + // e.getWindow().setVisible(false); + // frame.setVisible(true); + // frame.setSize(e.getWindow().getSize()); + // } + // + // public void windowClosed(WindowEvent e) { + // frame.setVisible(false); + // } + // } + + // /** + // * This method initializes jMenuToolsOption + // * + // * @return javax.swing.JMenuItem + // */ + // private javax.swing.JMenuItem getJMenuToolsOption() { + // if (jMenuToolsOption == null) { + // jMenuToolsOption = new javax.swing.JMenuItem(); + // jMenuToolsOption.setText("JAS options"); + // jMenuToolsOption + // .addActionListener(new java.awt.event.ActionListener() { + // public void actionPerformed(java.awt.event.ActionEvent e) { + // controller.editProperties(); + // } + // }); + // } + // return jMenuToolsOption; + // } + + /** + * This method initializes jMenuHelpAbout + * + * @return javax.swing.JMenuItem + */ + private javax.swing.JMenuItem getJMenuHelpAbout() { + if (jMenuHelpAbout == null) { + jMenuHelpAbout = new javax.swing.JMenuItem(); + jMenuHelpAbout.setText("About JAS-mine"); + jMenuHelpAbout.setIcon(new javax.swing.ImageIcon(getClass() + .getResource("/microsim/gui/icons/msIco.gif"))); + jMenuHelpAbout + .addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + (new AboutFrame()).setVisible(true); + } + }); + } + return jMenuHelpAbout; + } + + // /** + // * This method initializes jMenuSimulationEngine + // * + // * @return javax.swing.JMenuItem + // */ + // private javax.swing.JMenuItem getJMenuSimulationEngine() { + // if (jMenuSimulationEngine == null) { + // jMenuSimulationEngine = new javax.swing.JMenuItem(); + // jMenuSimulationEngine.setText("Show engine status"); + // jMenuSimulationEngine + // .addActionListener(new java.awt.event.ActionListener() { + // public void actionPerformed(java.awt.event.ActionEvent e) { + // controller.showProperties(); + // } + // }); + // } + // return jMenuSimulationEngine; + // } + + // /** + // * This method initializes jMenuHelpWebSite + // * + // * @return javax.swing.JMenuItem + // */ + // private javax.swing.JMenuItem getJMenuHelpWebSite() { + // if (jMenuHelpWebSite == null) { + // jMenuHelpWebSite = new javax.swing.JMenuItem(); + // jMenuHelpWebSite.setText("JAS web site"); + // jMenuHelpWebSite + // .addActionListener(new java.awt.event.ActionListener() { + // public void actionPerformed(java.awt.event.ActionEvent e) { + // controller.navigateWebSite(); + // } + // }); + // + // } + // return jMenuHelpWebSite; + // } + + /** + * This method initializes jSplitInternalDesktop + * + * @return javax.swing.JSplitPane + */ + private javax.swing.JSplitPane getJSplitInternalDesktop() { + if (jSplitInternalDesktop == null) { + jSplitInternalDesktop = new javax.swing.JSplitPane(); + jSplitInternalDesktop.setTopComponent(getJDesktopPane()); + jSplitInternalDesktop.setBottomComponent(null); + jSplitInternalDesktop.setSize(31, 59); + jSplitInternalDesktop + .setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); + jSplitInternalDesktop.setOneTouchExpandable(true); + } + return jSplitInternalDesktop; + } + + public void log(String message) { + consoleWindow.log(message); + } +} // @jve:visual-info decl-index=0 visual-constraint="10,10" diff --git a/src/main/java/microsim/gui/shell/MultiRunFrame.java b/src/main/java/microsim/gui/shell/MultiRunFrame.java new file mode 100644 index 00000000..a0d8aa71 --- /dev/null +++ b/src/main/java/microsim/gui/shell/MultiRunFrame.java @@ -0,0 +1,209 @@ +package microsim.gui.shell; + +import java.awt.FlowLayout; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; + +import microsim.engine.EngineListener; +import microsim.engine.MultiRun; +import microsim.engine.MultiRunListener; +import microsim.engine.SimulationEngine; +import microsim.event.SystemEventType; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jfree.ui.tabbedui.VerticalLayout; + +/** + * Not of interest for users. This class implements the multi run control panel + * shown by JAS when a MultiRun class is executed. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright: Copyright (C) 2002 Michele Sonnessa + *

+ *

+ * Company: + *

+ * + * @author Michele Sonnessa + */ + +public class MultiRunFrame extends JFrame implements MultiRunListener, EngineListener { + + private static Logger log = LogManager.getLogger(MultiRunFrame.class); + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + private VerticalLayout borderLayout1 = new VerticalLayout(); + private JPanel jPanelNorth = new JPanel(); + private JLabel jLblNumber = new JLabel(); + private JLabel jLblRunNb = new JLabel(); + private JLabel jLblCurrentStep = new JLabel(); + private JLabel jLblCurrentStepLabel = new JLabel(); + private JLabel jLblCurrentRun = new JLabel(); + private JLabel jLblCurrentRunLabel = new JLabel(); + private JProgressBar jBar = new JProgressBar(); + private JButton jBtnQuit = new JButton(); + private JPanel jPanelBtns = new JPanel(); + private JButton jBtnStart = new JButton(); + + private int forward = 1; + + private int maxRuns; + + private MultiRun test; + + public MultiRunFrame(MultiRun test, String title, int maxRuns) { + this.test = test; + this.maxRuns = maxRuns; + test.getEngineListeners().add(this); + test.getMultiRunListeners().add(this); + try { + jbInit(); + } catch (Exception e) { + e.printStackTrace(); + } + + this.setTitle(title); + setMaxRuns(maxRuns); + this.setVisible(true); + this.setResizable(false); + } + + private void jbInit() throws Exception { + this.getContentPane().setLayout(borderLayout1); + jPanelNorth.setLayout(new VerticalLayout()); + + JPanel h1 = new JPanel(new FlowLayout()); + jLblNumber.setText("0"); + jLblRunNb.setText("Current run number: "); + h1.add(jLblRunNb); + h1.add(jLblNumber); + + JPanel h2 = new JPanel(new FlowLayout()); + jLblCurrentStep.setText("0"); + jLblCurrentStepLabel.setText("Current run step: "); + h2.add(jLblCurrentStepLabel); + h2.add(jLblCurrentStep); + + JPanel h3 = new JPanel(new FlowLayout()); + jLblCurrentRun.setText(""); + jLblCurrentRunLabel.setText("Current step: "); + h3.add(jLblCurrentRunLabel); + h3.add(jLblCurrentRun); + + jBtnQuit.setText("Quit"); + jBtnQuit.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnQuit_actionPerformed(e); + } + }); + jBtnStart.setText("Start"); + jBtnStart.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnStart_actionPerformed(e); + } + }); + + jPanelBtns.add(jBtnStart, null); + this.getContentPane().add(jPanelNorth); + jPanelNorth.add(h1, null); + jPanelNorth.add(h2, null); + jPanelNorth.add(h3, null); + jPanelNorth.add(new JLabel("-"), null); + + this.getContentPane().add(jBar); + this.getContentPane().add(new JLabel("-")); + + this.getContentPane().add(jPanelBtns); + jPanelBtns.add(jBtnQuit, null); + + setSize(300, 180); + int x = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth(); + int y = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight(); + setLocation((x - 300) / 2, (y - 300) / 2); + } + + public void updateModelNumber(int currentRun, SimulationEngine engine) { + jLblNumber.setText(currentRun + ""); + jLblCurrentRun.setText(engine.getMultiRunId()); + } + + void jBtnQuit_actionPerformed(ActionEvent e) { + System.exit(0); + } + + public void setMaxRuns(int maxRuns) { + jBar.setMaximum(maxRuns); + } + + public void updateBar() { + if (jBar.getValue() == 0 || jBar.getValue() == jBar.getMaximum()) { + if (forward == 1) + forward = -1; + else + forward = 1; + } + + jBar.setValue(jBar.getValue() + forward); + + repaint(); + } + + private class Timer extends Thread { + private MultiRunFrame caller; + + public Timer(MultiRunFrame caller) { + this.caller = caller; + } + + public void run() { + while (true) { + caller.updateBar(); + try { + sleep(200); + } catch (Exception e) { + } + } + } + } + + void jBtnStart_actionPerformed(ActionEvent e) { + jBtnStart.setEnabled(false); + Timer tm = new Timer(this); + tm.start(); + test.start(); + } + + public void beforeSimulationStart(SimulationEngine engine) { + if (maxRuns > 0 && test.getCounter() > maxRuns) { + log.info("Maximum run number reached. Bye"); + System.exit(0); + } + + updateModelNumber(test.getCounter(), engine); + } + + public void afterSimulationCompleted(SimulationEngine engine) { + + } + + public void onEngineEvent(SystemEventType event) { + if (event.equals(SystemEventType.Step)) + jLblCurrentStep.setText(SimulationEngine.getInstance().getTime() + ""); + } +} diff --git a/src/main/java/microsim/gui/shell/SimulationWindow.java b/src/main/java/microsim/gui/shell/SimulationWindow.java new file mode 100644 index 00000000..4d7b1cd9 --- /dev/null +++ b/src/main/java/microsim/gui/shell/SimulationWindow.java @@ -0,0 +1,132 @@ +package microsim.gui.shell; + +import java.awt.Container; +import java.awt.Rectangle; + +/** + * SimWindow keeps preferred dimensions of a simulation windows. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class SimulationWindow { + private Container window; + private String key; + private String model; + private Rectangle defaultPosition; + + /** + * Create a new window container with the given parameters. + * + * @param model The id of the model owner + * @param key The key used to store the element in the HashMap + * @param window The window to be managed + */ + public SimulationWindow(String model, String key, Container window) { + this.window = window; + this.model = model; + this.key = key; + } + + /** + * Return the dimension of the managed window. If the window is not yet created + * the method + * returns the default bounds. + * + * @return The window dimensions if present or the default ones if not. + */ + public Rectangle getBounds() { + if (window == null) + return getDefaultPosition(); + else + return window.getBounds(); + } + + /** + * Return the key of the SimWindow object + * + * @return The key value. + */ + public String getKey() { + return key; + } + + /** + * The owner model id + * + * @return A string representing the model id. + */ + public String getModel() { + return model; + } + + /** + * Attach a window to the SimWindow container + * + * @param container A container window object + */ + public void setWindow(Container container) { + window = container; + } + + /** + * Return the the SimWindow container + */ + public Container getWindow() { + return window; + } + + /** + * Return the default bounds for the window + * + * @return The default position of the window + */ + public Rectangle getDefaultPosition() { + return defaultPosition; + } + + /** + * Set the default dimensions + * + * @param rectangle The new default bounds of the window + */ + public void setDefaultPosition(Rectangle rectangle) { + defaultPosition = rectangle; + } + + /** + * + */ + public String toString() { + return key; + } + +} diff --git a/src/main/java/microsim/gui/shell/parameter/DescriptiveSwingMetawidget.java b/src/main/java/microsim/gui/shell/parameter/DescriptiveSwingMetawidget.java new file mode 100644 index 00000000..eb17c050 --- /dev/null +++ b/src/main/java/microsim/gui/shell/parameter/DescriptiveSwingMetawidget.java @@ -0,0 +1,26 @@ +package microsim.gui.shell.parameter; + +import java.awt.Component; +import java.util.Map; + +import javax.swing.JComponent; + +import org.metawidget.swing.SwingMetawidget; + +public class DescriptiveSwingMetawidget extends SwingMetawidget { + + private static final long serialVersionUID = 1L; + + @Override + protected void layoutWidget(Component component, String elementName, + Map attributes) { + + super.layoutWidget(component, elementName, attributes); + + if (component == null) + return; + + ((JComponent) component).setToolTipText(attributes.get("tooltip")); + } + +} diff --git a/src/main/java/microsim/gui/shell/parameter/MetawidgetBinder.java b/src/main/java/microsim/gui/shell/parameter/MetawidgetBinder.java new file mode 100644 index 00000000..a4c3a794 --- /dev/null +++ b/src/main/java/microsim/gui/shell/parameter/MetawidgetBinder.java @@ -0,0 +1,417 @@ +// Metawidget +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package microsim.gui.shell.parameter; + +import static org.metawidget.inspector.InspectionResultConstants.NAME; +import static org.metawidget.inspector.InspectionResultConstants.NO_SETTER; +import static org.metawidget.inspector.InspectionResultConstants.PROPERTY; +import static org.metawidget.inspector.InspectionResultConstants.TRUE; + +import java.awt.Component; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Set; + +import javax.swing.JComponent; +import javax.swing.JScrollPane; + +import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.beanutils.ConvertUtils; +import org.apache.commons.beanutils.PropertyUtils; +import org.metawidget.swing.SwingMetawidget; +import org.metawidget.swing.widgetprocessor.binding.BindingConverter; +import org.metawidget.util.CollectionUtils; +import org.metawidget.util.simple.PathUtils; +import org.metawidget.util.simple.StringUtils; +import org.metawidget.widgetprocessor.iface.AdvancedWidgetProcessor; +import org.metawidget.widgetprocessor.iface.WidgetProcessorException; + +/** + * Property binding implementation based on BeanUtils. + *

+ * This implementation recognizes the following + * SwingMetawidget.setParameter + * parameters: + *

+ *

    + *
  • propertyStyle - either PROPERTYSTYLE_JAVABEAN + * (default) or + * PROPERTYSTYLE_SCALA (for Scala-style getters and setters). + *
+ *

+ * Note: BeanUtils does not bind actions, such as invoking + * a method when a + * JButton is pressed. For that, see + * ReflectionBindingProcessor and + * MetawidgetActionStyle or + * SwingAppFrameworkActionStyle. + * + * @author Richard Kennard, Stefan Ackermann + */ + +public class MetawidgetBinder + implements AdvancedWidgetProcessor, BindingConverter { + + // + // Constructor + // + + public MetawidgetBinder() { + + } + + // + // Public methods + // + + public void onStartBuild(SwingMetawidget metawidget) { + + metawidget.putClientProperty(MetawidgetBinder.class, null); + } + + public JComponent processWidget(JComponent component, String elementName, Map attributes, + SwingMetawidget metawidget) { + + JComponent componentToBind = component; + + // Unwrap JScrollPanes (for JTextAreas etc) + + if (componentToBind instanceof JScrollPane) { + componentToBind = (JComponent) ((JScrollPane) componentToBind).getViewport().getView(); + } + + // Nested Metawidgets are not bound, only remembered + + if (componentToBind instanceof SwingMetawidget) { + + State state = getState(metawidget); + + if (state.nestedMetawidgets == null) { + state.nestedMetawidgets = CollectionUtils.newHashSet(); + } + + state.nestedMetawidgets.add((SwingMetawidget) component); + return component; + } + + // Determine value property + + String componentProperty = metawidget.getValueProperty(componentToBind); + + if (componentProperty == null) { + return component; + } + + String path = metawidget.getPath(); + + if (PROPERTY.equals(elementName)) { + path += StringUtils.SEPARATOR_FORWARD_SLASH_CHAR + attributes.get(NAME); + } + + try { + // Convert 'com.Foo/bar/baz' into BeanUtils notation 'bar.baz' + + String names = PathUtils.parsePath(path, StringUtils.SEPARATOR_FORWARD_SLASH_CHAR).getNames() + .replace(StringUtils.SEPARATOR_FORWARD_SLASH_CHAR, StringUtils.SEPARATOR_DOT_CHAR); + + Object sourceValue; + + try { + sourceValue = retrieveValueFromObject(metawidget, metawidget.getToInspect(), names); + } catch (NoSuchMethodException e) { + throw WidgetProcessorException.newException("Property '" + names + "' has no getter"); + } + + SavedBinding binding = new SavedBinding(componentToBind, componentProperty, names, + TRUE.equals(attributes.get(NO_SETTER))); + saveValueToWidget(binding, sourceValue); + + State state = getState(metawidget); + + if (state.bindings == null) { + state.bindings = CollectionUtils.newHashSet(); + } + + state.bindings.add(binding); + } catch (Exception e) { + throw WidgetProcessorException.newException(e); + } + + return component; + } + + /** + * Rebinds the Metawidget to the given Object. + *

+ * This method is an optimization that allows clients to load a new object into + * the binding + * without calling setToInspect, and therefore without reinspecting the + * object or + * recreating the components. It is the client's responsbility to ensure the + * rebound object is + * compatible with the original setToInspect. + */ + + public void rebind(Object toRebind, SwingMetawidget metawidget) { + + metawidget.updateToInspectWithoutInvalidate(toRebind); + State state = getState(metawidget); + + // Our bindings + + if (state.bindings != null) { + try { + for (SavedBinding binding : state.bindings) { + Object sourceValue; + String names = binding.getNames(); + + try { + sourceValue = retrieveValueFromObject(metawidget, toRebind, names); + } catch (NoSuchMethodException e) { + throw WidgetProcessorException.newException("Property '" + names + "' has no getter"); + } + + saveValueToWidget(binding, sourceValue); + } + } catch (Exception e) { + throw WidgetProcessorException.newException(e); + } + } + + // Nested Metawidgets + + if (state.nestedMetawidgets != null) { + for (SwingMetawidget nestedMetawidget : state.nestedMetawidgets) { + rebind(toRebind, nestedMetawidget); + } + } + } + + public void save(SwingMetawidget metawidget) { + + State state = getState(metawidget); + + // Our bindings + + if (state.bindings != null) { + try { + for (SavedBinding binding : state.bindings) { + if (!binding.isSettable()) { + continue; + } + + Object componentValue = retrieveValueFromWidget(binding); + saveValueToObject(metawidget, binding.getNames(), componentValue); + } + } catch (Exception e) { + throw WidgetProcessorException.newException(e); + } + } + + // Nested Metawidgets + + if (state.nestedMetawidgets != null) { + for (SwingMetawidget nestedMetawidget : state.nestedMetawidgets) { + save(nestedMetawidget); + } + } + } + + public Object convertFromString(String value, Class expectedType) { + + return ConvertUtils.convert(value, expectedType); + } + + public void onEndBuild(SwingMetawidget metawidget) { + + // Do nothing + } + + // + // Protected methods + // + + /** + * Retrieve value identified by the given names from the given source. + *

+ * Clients may override this method to incorporate their own getter convention. + * + * @param metawidget + * Metawidget to retrieve value from + */ + + protected Object retrieveValueFromObject(SwingMetawidget metawidget, Object source, String names) throws Exception { + + return BeanUtils.getProperty(source, names); + /* + * Field field = source.getClass().getField(names); + * field.setAccessible(true); + * return field.get(source); + */ + + } + + /** + * Save the given value into the given source at the location specified by the + * given names. + *

+ * Clients may override this method to incorporate their own setter convention. + * + * @param componentValue + * the raw value from the JComponent + */ + + @SuppressWarnings("unchecked") + protected void saveValueToObject(SwingMetawidget metawidget, String names, Object componentValue) + throws Exception { + + Object source = metawidget.getToInspect(); + + Field field = source.getClass().getDeclaredField(names); + field.setAccessible(true); + + if (field.getType().isEnum()) { + if (componentValue != null) { + Class c = field.getType(); + @SuppressWarnings("rawtypes") + Class ce = (Class) c; + Enum value1 = Enum.valueOf(ce, componentValue.toString()); + BeanUtils.setProperty(source, names, value1); + } else { + BeanUtils.setProperty(source, names, componentValue); + } + } else { + BeanUtils.setProperty(source, names, componentValue); + } + + /* + * Field field = source.getClass().getField(names); + * field.setAccessible(true); + * field.set(source, componentValue); + */ + + } + + protected Object retrieveValueFromWidget(SavedBinding binding) + throws Exception { + + return PropertyUtils.getProperty(binding.getComponent(), binding.getComponentProperty()); + } + + protected void saveValueToWidget(SavedBinding binding, Object sourceValue) + throws Exception { + + if (sourceValue.getClass().isEnum()) + BeanUtils.setProperty(binding.getComponent(), binding.getComponentProperty(), sourceValue.toString()); + else + BeanUtils.setProperty(binding.getComponent(), binding.getComponentProperty(), sourceValue); + } + + // + // Private methods + // + + private State getState(SwingMetawidget metawidget) { + + State state = (State) metawidget.getClientProperty(MetawidgetBinder.class); + + if (state == null) { + state = new State(); + metawidget.putClientProperty(MetawidgetBinder.class, state); + } + + return state; + } + + // + // Inner class + // + + /** + * Simple, lightweight structure for saving state. + */ + + /* package private */static class State { + + /* package private */Set bindings; + + /* package private */Set nestedMetawidgets; + } + + static class SavedBinding { + + // + // + // Private members + // + // + + private Component mComponent; + + private String mComponentProperty; + + private String mNames; + + private boolean mNoSetter; + + // + // + // Constructor + // + // + + public SavedBinding(Component component, String componentProperty, String names, boolean noSetter) { + + mComponent = component; + mComponentProperty = componentProperty; + mNames = names; + mNoSetter = noSetter; + } + + // + // + // Public methods + // + // + + public Component getComponent() { + + return mComponent; + } + + public String getComponentProperty() { + + return mComponentProperty; + } + + /** + * Property names into the source object. + *

+ * Stored in BeanUtils style foo.bar.baz. + */ + + public String getNames() { + + return mNames; + } + + public boolean isSettable() { + + return !mNoSetter; + } + } +} diff --git a/src/main/java/microsim/gui/shell/parameter/ParameterFrame.java b/src/main/java/microsim/gui/shell/parameter/ParameterFrame.java new file mode 100644 index 00000000..db363495 --- /dev/null +++ b/src/main/java/microsim/gui/shell/parameter/ParameterFrame.java @@ -0,0 +1,128 @@ +package microsim.gui.shell.parameter; + +import java.awt.Color; +import java.awt.Font; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.swing.*; +import microsim.annotation.GUIparameter; +import microsim.annotation.ModelParameter; +import microsim.gui.shell.MicrosimShell; + +import org.metawidget.inspector.composite.CompositeInspector; +import org.metawidget.inspector.composite.CompositeInspectorConfig; +import org.metawidget.inspector.impl.BaseObjectInspector; +import org.metawidget.inspector.impl.propertystyle.Property; +import org.metawidget.swing.SwingMetawidget; +import org.metawidget.swing.widgetprocessor.binding.beansbinding.BeansBindingProcessor; +import org.metawidget.swing.widgetprocessor.binding.beansbinding.BeansBindingProcessorConfig; +import org.metawidget.util.CollectionUtils; + +@SuppressWarnings("deprecation") +public class ParameterFrame extends JInternalFrame { + + private static final long serialVersionUID = 1L; + + private Object target; + + private MetawidgetBinder binder; + + private SwingMetawidget metawidget; + + public ParameterFrame(Object target) { + super(); + + this.target = target; + + try { + jbInit(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void jbInit() throws Exception { + this.setResizable(true); + this.setTitle(target.getClass().getSimpleName() + "'s parameters"); + + List fields = ParameterInspector.extractModelParameters(target.getClass()); + + metawidget = new DescriptiveSwingMetawidget(); + CompositeInspectorConfig inspectorConfig = new CompositeInspectorConfig().setInspectors( + new ParameterInspector(), + new TooltipInspector(fields)); + + binder = new MetawidgetBinder(); + metawidget.addWidgetProcessor(binder); + + // //The following code allows automatic synchronization between the GUI + // parameters and the model. If you want the user to have to click on 'Update + // Parameters' button, comment this out. + // metawidget.addWidgetProcessor( + // new BeansBindingProcessor( + // new BeansBindingProcessorConfig().setUpdateStrategy( + // org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE )) ); + + metawidget.setInspector(new CompositeInspector(inspectorConfig)); + metawidget.setToInspect(target); + + setSize((int) (MicrosimShell.scale * 320), + Math.min((int) (MicrosimShell.scale * Math.max(30 + 26 * fields.size(), 90)), 500)); + JScrollPane scrollP = new JScrollPane(metawidget); + + if (metawidget.getComponentCount() > 0) + scrollP.getViewport().setBackground(metawidget.getComponent(0).getBackground()); + getContentPane().add(scrollP); + setVisible(true); + } + + public void save() { + binder.save(metawidget); + } + + public static class TooltipInspector + extends BaseObjectInspector { + + Map guiParamDescriptions; + + TooltipInspector(List fields) { + + guiParamDescriptions = CollectionUtils.newHashMap(); + for (Field f : fields) { + String description = null; + try { + description = f.getAnnotation(GUIparameter.class).description(); + if (description == null) + description = f.getAnnotation(ModelParameter.class).description(); // Old deprecated version + if (description != null) { + guiParamDescriptions.put(f.getName(), description); + } + } catch (NullPointerException e) { + // Do nothing + } + } + } + + // @Override + protected Map inspectProperty(Property property) + throws Exception { + + Map attributes = CollectionUtils.newHashMap(); + + // ModelParameter tooltip = property.getAnnotation( ModelParameter.class ); + // //Always returns null for some reason - property doesn't have the annotation + // information + + String description = guiParamDescriptions.get(property.getName()); + // if ( tooltip != null ) { //Always null as property doesn't contain annotation + if (description != null) { + attributes.put("tooltip", description); + } + + return attributes; + } + } +} diff --git a/src/main/java/microsim/gui/shell/parameter/ParameterInspector.java b/src/main/java/microsim/gui/shell/parameter/ParameterInspector.java new file mode 100644 index 00000000..e40f33ac --- /dev/null +++ b/src/main/java/microsim/gui/shell/parameter/ParameterInspector.java @@ -0,0 +1,78 @@ +package microsim.gui.shell.parameter; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import microsim.annotation.GUIparameter; +import microsim.annotation.ModelParameter; + +import org.metawidget.inspector.iface.Inspector; + +@SuppressWarnings("deprecation") +public class ParameterInspector implements Inspector { + + // @Override + public String inspect(Object object, String arg1, String... arg2) { + Class clazz = object.getClass(); + + StringBuffer buf = new StringBuffer(); + buf.append(""); + buf.append(""); + + List fields = ParameterInspector.extractModelParameters(clazz); + for (Field field : fields) { + StringBuffer extra = new StringBuffer(); + + if (field.getType().isEnum()) { + String comma = ""; + extra.append("lookup=\""); + for (Object constz : field.getType().getEnumConstants()) { + extra.append(comma).append(constz); + comma = ","; + } + extra.append("\""); + } + + if (field.getType().equals(Boolean.class)) { + buf.append(""); + } else { + buf.append(""); + } + } + + buf.append(""); + buf.append(""); + + return buf.toString(); + } + + public static List extractModelParameters(Class clazz) { + List collectedFields = new ArrayList(); + + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + Annotation annos[] = field.getAnnotations(); + for (Annotation anno : annos) { + if ((anno.annotationType().equals(GUIparameter.class)) + || (anno.annotationType().equals(ModelParameter.class))) { + collectedFields.add(field); + } + } + } + + return collectedFields; + } +} diff --git a/src/main/java/microsim/gui/space/CellObjectChooser.java b/src/main/java/microsim/gui/space/CellObjectChooser.java new file mode 100644 index 00000000..079f3d0a --- /dev/null +++ b/src/main/java/microsim/gui/space/CellObjectChooser.java @@ -0,0 +1,131 @@ +package microsim.gui.space; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JList; +import javax.swing.JPanel; + +import microsim.gui.GuiUtils; + +/** + * Not of interest for users. A window used by LayeredSurfaceFrame + * to choose the object to be probed when + * user click on a cell of a LayerMultiObjectGridDrawer. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class CellObjectChooser extends JDialog { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + JPanel panel1 = new JPanel(); + BorderLayout borderLayout1 = new BorderLayout(); + JList jListObjects = new JList(); + JPanel jPanel1 = new JPanel(); + JButton jBtnCancel = new JButton(); + JButton jBtnOK = new JButton(); + + public CellObjectChooser(Object[] objs, Frame frame, String title, boolean modal) { + super(frame, title, modal); + jListObjects.setListData(objs); + try { + jbInit(); + pack(); + } catch (Exception ex) { + ex.printStackTrace(); + } + + Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); + this.setLocation((d.width - getSize().width) / 2, + (d.height - getSize().height) / 2); + } + + public CellObjectChooser(Object[] objs) { + this(objs, null, "", false); + } + + void jbInit() throws Exception { + panel1.setLayout(borderLayout1); + jBtnCancel.setText("Cancel"); + jBtnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnCancel_actionPerformed(e); + } + }); + jBtnOK.setText("Open probe"); + jBtnOK.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnOK_actionPerformed(e); + } + }); + jListObjects.setBorder(BorderFactory.createEtchedBorder()); + jListObjects.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(MouseEvent e) { + jListObjects_mouseClicked(e); + } + }); + getContentPane().add(panel1); + panel1.add(jListObjects, BorderLayout.CENTER); + this.getContentPane().add(jPanel1, BorderLayout.SOUTH); + jPanel1.add(jBtnCancel, null); + jPanel1.add(jBtnOK, null); + } + + void jListObjects_mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) + if (jListObjects.getSelectedValue() != null) { + GuiUtils.openProbe(jListObjects.getSelectedValue(), "Selected object"); + dispose(); + } + } + + void jBtnCancel_actionPerformed(ActionEvent e) { + dispose(); + } + + void jBtnOK_actionPerformed(ActionEvent e) { + if (jListObjects.getSelectedValue() != null) + GuiUtils.openProbe(jListObjects.getSelectedValue(), "Selected object"); + else + return; + dispose(); + } +} diff --git a/src/main/java/microsim/gui/space/ILayerDrawer.java b/src/main/java/microsim/gui/space/ILayerDrawer.java new file mode 100644 index 00000000..baab1211 --- /dev/null +++ b/src/main/java/microsim/gui/space/ILayerDrawer.java @@ -0,0 +1,70 @@ +package microsim.gui.space; + +import java.awt.Graphics; + +/** + * An interface used by LayeredSurfaceFrame to delegate + * the rendering of a layer. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public interface ILayerDrawer extends ILayerMouseListener { + /** + * Return the description of the layer. + * + * @return The string describing the layer. + */ + public String getDescription(); + + /** + * Return if the layer is currently displayed. + * + * @return True if the layer is displayed. + */ + public boolean isDisplayed(); + + /** + * Set the display status. + * + * @param display If true the layer will be displayed. + */ + public void setDisplay(boolean display); + + /** + * Paint the layer on the screen. + * + * @param g The current graphics device. + * @param cellLen The length of a cell in pixels. + */ + public void paint(Graphics g, int cellLen); + +} diff --git a/src/main/java/microsim/gui/space/ILayerMouseListener.java b/src/main/java/microsim/gui/space/ILayerMouseListener.java new file mode 100644 index 00000000..7f346e2c --- /dev/null +++ b/src/main/java/microsim/gui/space/ILayerMouseListener.java @@ -0,0 +1,70 @@ +package microsim.gui.space; + +/** + * An interface used by LayerDrawer to manage + * the mouse events. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public interface ILayerMouseListener { + /** + * Notify a double click event on a specific cell. + * + * @param atX The x coordinate of the clicked cell. + * @param atY The y coordinate of the clicked cell. + * @return True if the layer intercepted the event. False if + * the notify has been ignored. + */ + public boolean performDblClickActionAt(int atX, int atY); + + /** + * Notify a right button click event on a specific cell. + * + * @param atX The x coordinate of the clicked cell. + * @param atY The y coordinate of the clicked cell. + * @return True if the layer intercepted the event. False if + * the notify has been ignored. + */ + public boolean performRightClickActionAt(int atX, int atY); + + /** + * Notify a mouse dragging action. + * + * @param fromX The x coordinate of the starting cell. + * @param fromY The y coordinate of the starting cell. + * @param toX The x coordinate of the target cell. + * @param toY The y coordinate of the target cell. + * @return True if the layer intercepted the event. False if + * the notify has been ignored. + */ + public boolean performMouseMovedFromTo(int fromX, int fromY, int toX, int toY); +} diff --git a/src/main/java/microsim/gui/space/LayerDblGridDrawer.java b/src/main/java/microsim/gui/space/LayerDblGridDrawer.java new file mode 100644 index 00000000..c0ebe347 --- /dev/null +++ b/src/main/java/microsim/gui/space/LayerDblGridDrawer.java @@ -0,0 +1,384 @@ +package microsim.gui.space; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import java.util.Arrays; + +import microsim.gui.colormap.ColorMap; +import microsim.space.DoubleSpace; + +/** + * It is able to draw a DblGrid on a LayeredSurfaceFrame using + * a ColorMap to render the values contained by the cell with + * a specific color.
+ * This class builds an image when created and every time is updated + * it modifies the parts of the images that are changed. + * It is very fast when images do not change to frequently. + * In order to let the painter to go faster it is useful to + * reduce the number of color gradients in the ColorMap. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class LayerDblGridDrawer implements ILayerDrawer { + private double[] m; + private ColorMap color; + int[] trasparencyColor; + private int xSize, ySize; + private int cellSize = 4; + + private boolean isDisplayed = true; + private String description; + + private int[] stateBuffer; + private BufferedImage img; + + private ILayerMouseListener mouseListener = null; + + private void buildBufferImage() { + WritableRaster raster = img.getRaster(); + stateBuffer = new int[xSize * ySize]; + + int colorDepth; + if (trasparencyColor == null) + colorDepth = 3; + else + colorDepth = 4; + + int[] pixels = new int[colorDepth * cellSize * cellSize]; + + int[] currColor = null; + int currIndex = -1; + + int k = 0; + for (int j = 0; j < ySize; j++) + for (int i = 0; i < xSize; i++) { + currIndex = color.getColorIndex(m[k]); + currColor = color.getColorComponents(currIndex); + stateBuffer[k] = currIndex; + int XX = i * cellSize; + int YY = j * cellSize; + + for (int z = 0; z < pixels.length; z += colorDepth) { + pixels[z] = currColor[0]; + pixels[z + 1] = currColor[1]; + pixels[z + 2] = currColor[2]; + if (colorDepth == 4) + if (Arrays.equals(currColor, trasparencyColor)) + pixels[z + 3] = 0; + else + pixels[z + 3] = 255; + } + + raster.setPixels(XX, YY, cellSize, cellSize, pixels); + k++; + } + } + + /** + * Create a double layer drawer using values taken from an array of + * doubles and a given IColorMap. + * + * @param name The string describing the layer. + * @param matrix An array of doubles of width * height length. + * @param width The width of the grid. + * @param height The height of the grid. + * @param colorRange The IColorMap used to map values to colors. + */ + public LayerDblGridDrawer(String name, double[] matrix, int width, int height, + ColorMap colorRange) { + m = matrix; + description = name; + color = colorRange; + xSize = width; + ySize = height; + trasparencyColor = null; + + img = new BufferedImage(xSize * cellSize, ySize * cellSize, BufferedImage.TYPE_INT_RGB); + buildBufferImage(); + } + + /** + * Create a double layer drawer using values taken from a DblGrid matrix and + * a given IColorMap. + * + * @param name The string describing the layer. + * @param matrix A DblGrid object. + * @param colorRange The IColorMap used to map values to colors. + */ + public LayerDblGridDrawer(String name, DoubleSpace matrix, ColorMap colorRange) { + this(name, matrix.getMatrix(), matrix.getXSize(), matrix.getYSize(), colorRange); + } + + /** + * Create a double layer drawer using values taken from an array of + * doubles and a given IColorMap. It allows to define a trasparency color. + * Every time the drawer has to plot the trasparentColor it stops drawing, + * so the cell of underneath layer becomes visible. + * + * @param name The string describing the layer. + * @param matrix An array of doubles of width * height length. + * @param width The width of the grid. + * @param height The height of the grid. + * @param colorRange The IColorMap used to map values to colors. + * @param trasparentColor A color + */ + public LayerDblGridDrawer(String name, double[] matrix, int width, int height, + ColorMap colorRange, Color trasparentColor) { + m = matrix; + description = name; + color = colorRange; + xSize = width; + ySize = height; + trasparencyColor = new int[3]; + trasparencyColor[0] = trasparentColor.getRed(); + trasparencyColor[1] = trasparentColor.getGreen(); + trasparencyColor[2] = trasparentColor.getBlue(); + + img = new BufferedImage(xSize * cellSize, ySize * cellSize, BufferedImage.TYPE_INT_ARGB); + buildBufferImage(); + } + + /** + * Create a double layer drawer using values using values taken from a DblGrid + * matrix and a given IColorMap. It allows to define a trasparency color. + * Every time the drawer has to plot the trasparentColor it stops drawing, + * so the cell of underneath layer becomes visible. + * + * @param name The string describing the layer. + * @param matrix A DblGrid object. + * @param colorRange The IColorMap used to map values to colors. + * @param trasparentColor A color + */ + public LayerDblGridDrawer(String name, DoubleSpace matrix, + ColorMap colorRange, Color trasparentColor) { + this(name, matrix.getMatrix(), matrix.getXSize(), matrix.getYSize(), + colorRange, trasparentColor); + } + + // Implementing LayerDrawerInterface interface + + /** + * Draw the layer using the given cell length. + * + * @param g The graphic context passed by container. + * @param cellLen The length of a cell in pixels. + */ + public void paint(Graphics g, int cellLen) { + if (trasparencyColor != null) + paintWithTrasparency(g, cellLen); + else + paintWithoutTrasparency(g, cellLen); + } + + private void setCellLenght(int cellLength) { + cellSize = cellLength; + if (trasparencyColor == null) + img = new BufferedImage(xSize * cellSize, ySize * cellSize, BufferedImage.TYPE_INT_RGB); + else + img = new BufferedImage(xSize * cellSize, ySize * cellSize, BufferedImage.TYPE_INT_ARGB); + + buildBufferImage(); + } + + private void paintWithoutTrasparency(Graphics g, int cellLen) { + WritableRaster raster = img.getRaster(); + int[] pixels = new int[3 * cellLen * cellLen]; + + int[] currColor; + int currIndex; + + if (cellSize != cellLen) + setCellLenght(cellLen); + + int k = 0; + for (int j = 0; j < ySize; j++) + for (int i = 0; i < xSize; i++) + + { + currIndex = color.getColorIndex(m[k]); + if (currIndex != stateBuffer[k]) { + currColor = color.getColorComponents(currIndex); + stateBuffer[k] = currIndex; + int XX = i * cellLen; + int YY = j * cellLen; + + for (int z = 0; z < pixels.length; z += 3) { + pixels[z] = currColor[0]; + pixels[z + 1] = currColor[1]; + pixels[z + 2] = currColor[2]; + } + + raster.setPixels(XX, YY, cellLen, cellLen, pixels); + } + k++; + } + + g.drawImage(img, 0, 0, null); + } + + private void paintWithTrasparency(Graphics g, int cellLen) { + WritableRaster raster = img.getRaster(); + int[] pixels = new int[4 * cellLen * cellLen]; + int[] currColor; + int currIndex; + int alpha = 0; + + if (cellSize != cellLen) + setCellLenght(cellLen); + + int k = 0; + for (int j = 0; j < ySize; j++) + for (int i = 0; i < xSize; i++) { + currIndex = color.getColorIndex(m[k]); + if (currIndex != stateBuffer[k]) { + currColor = color.getColorComponents(currIndex); + stateBuffer[k] = currIndex; + int XX = i * cellLen; + int YY = j * cellLen; + + if (Arrays.equals(currColor, trasparencyColor)) + alpha = 0; + else + alpha = 255; + + for (int z = 0; z < pixels.length; z += 4) { + pixels[z] = currColor[0]; + pixels[z + 1] = currColor[1]; + pixels[z + 2] = currColor[2]; + pixels[z + 3] = alpha; + } + + raster.setPixels(XX, YY, cellLen, cellLen, pixels); + } + k++; + } + + g.drawImage(img, 0, 0, null); + } + + /** + * Return if the layer is currently displayed on the LayeredSurfaceFrame. + * + * @return True if it is currently painted, false otherwise. + */ + public boolean isDisplayed() { + return isDisplayed; + } + + /** + * Decide if layer has to be painted or not. + * + * @param display True if you want the layer to be painted, false otherwise. + */ + public void setDisplay(boolean display) { + isDisplayed = display; + } + + /** + * Return the name of the layer. + * + * @return The name passed to the constructor. + */ + public String getDescription() { + return description; + } + + /** + * Set a manager for mouse events. If not defined, mouse events are + * managed by the class itself. + * + * @param listener A ILayerMouseListener object. + */ + public void setMouseListener(ILayerMouseListener listener) { + mouseListener = listener; + } + + /** + * If a mouse listener has been defined the double-click event, it is passed + * to it, otherwise it is shown a message box with the value contained + * by the clicked cell. + * + * @param atX The x coordinate of the clicked cell. + * @param atY The y coordinate of the clicked cell. + * @return always true if no mouse listener is defined. + * This value is used by caller to know if + * the layer wants to manage the event. + */ + public boolean performDblClickActionAt(int atX, int atY) { + if (mouseListener != null) + return mouseListener.performDblClickActionAt(atX, atY); + + javax.swing.JOptionPane.showMessageDialog(null, + "Value at(" + atX + ", " + atY + "): " + m[atY * xSize + atX], + "Probing " + getDescription(), + javax.swing.JOptionPane.INFORMATION_MESSAGE); + return true; + } + + /** + * If a mouse listener has been defined the right-click event, it is passed + * to it, otherwise it is returned false. + * + * @param atX The x coordinate of the clicked cell. + * @param atY The y coordinate of the clicked cell. + * @return always false if no mouse listener is defined. + * This value is used by caller to know if + * the layer wants to manage the event. + */ + public boolean performRightClickActionAt(int atX, int atY) { + if (mouseListener != null) + return mouseListener.performRightClickActionAt(atX, atY); + + return false; + } + + /** + * If a mouse listener has been defined the mouse dragging event, it is passed + * to it, otherwise it is returned false. + * + * @param fromX The x coordinate of the starting cell. + * @param fromY The y coordinate of the starting cell. + * @param toX The x coordinate of the last dragged cell. + * @param toY The y coordinate of the last dragged cell. + * @return always false if no mouse listener is defined. + * This value is used by caller to know if + * the layer wants to manage the event. + */ + public boolean performMouseMovedFromTo(int fromX, int fromY, int toX, int toY) { + if (mouseListener != null) + return mouseListener.performMouseMovedFromTo(fromX, fromY, toX, toY); + + return false; + } +} diff --git a/src/main/java/microsim/gui/space/LayerIntGridDrawer.java b/src/main/java/microsim/gui/space/LayerIntGridDrawer.java new file mode 100644 index 00000000..81532ef8 --- /dev/null +++ b/src/main/java/microsim/gui/space/LayerIntGridDrawer.java @@ -0,0 +1,383 @@ +package microsim.gui.space; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import java.util.Arrays; + +import microsim.gui.colormap.ColorMap; +import microsim.space.IntSpace; + +/** + * It is able to draw an IntGrid on a LayeredSurfaceFrame using + * a ColorMap to render the values contained by the cell with + * a specific color.
+ * This class builds an image when created and every time is updated + * it modifies the parts of the images that are changed. + * It is very fast when images do not change to frequently. + * In order to let the painter to go faster it is useful to + * reduce the number of color gradients in the ColorMap. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class LayerIntGridDrawer implements ILayerDrawer { + private int[] m; + private ColorMap color; + int[] trasparencyColor; + private int xSize, ySize; + private int cellSize = 4; + + private boolean isDisplayed = true; + private String description; + + private int[] stateBuffer; + private BufferedImage img; + + private ILayerMouseListener mouseListener = null; + + private void buildBufferImage() { + WritableRaster raster = img.getRaster(); + stateBuffer = new int[xSize * ySize]; + + int colorDepth; + if (trasparencyColor == null) + colorDepth = 3; + else + colorDepth = 4; + + int[] pixels = new int[colorDepth * cellSize * cellSize]; + + int[] currColor = null; + int currIndex = -1; + + int k = 0; + for (int j = 0; j < ySize; j++) + for (int i = 0; i < xSize; i++) { + currIndex = color.getColorIndex(m[k]); + currColor = color.getColorComponents(currIndex); + stateBuffer[k] = currIndex; + int XX = i * cellSize; + int YY = j * cellSize; + + for (int z = 0; z < pixels.length; z += colorDepth) { + pixels[z] = currColor[0]; + pixels[z + 1] = currColor[1]; + pixels[z + 2] = currColor[2]; + if (colorDepth == 4) + if (Arrays.equals(currColor, trasparencyColor)) + pixels[z + 3] = 0; + else + pixels[z + 3] = 255; + } + + raster.setPixels(XX, YY, cellSize, cellSize, pixels); + k++; + } + } + + /** + * Create a double layer drawer using values taken from an array of + * integers and a given IColorMap. + * + * @param name The string describing the layer. + * @param matrix An array of integers of width * height length. + * @param width The width of the grid. + * @param height The height of the grid. + * @param colorRange The IColorMap used to map values to colors. + */ + public LayerIntGridDrawer(String name, int[] matrix, int width, int height, + ColorMap colorRange) { + description = name; + m = matrix; + color = colorRange; + xSize = width; + ySize = height; + trasparencyColor = null; + + img = new BufferedImage(xSize * cellSize, ySize * cellSize, BufferedImage.TYPE_INT_RGB); + buildBufferImage(); + } + + /** + * Create a double layer drawer using values taken from an IntGrid matrix and + * a given IColorMap. + * + * @param name The string describing the layer. + * @param matrix An IntGrid object. + * @param colorRange The IColorMap used to map values to colors. + */ + public LayerIntGridDrawer(String name, IntSpace matrix, ColorMap colorRange) { + this(name, matrix.getMatrix(), matrix.getXSize(), matrix.getYSize(), colorRange); + } + + /** + * Create a double layer drawer using values taken from an array of + * integers and a given IColorMap. It allows to define a trasparency color. + * Every time the drawer has to plot the trasparentColor it stops drawing, + * so the cell of underneath layer becomes visible. + * + * @param name The string describing the layer. + * @param matrix An array of integers of width * height length. + * @param width The width of the grid. + * @param height The height of the grid. + * @param colorRange The IColorMap used to map values to colors. + * @param trasparentColor A color + */ + public LayerIntGridDrawer(String name, int[] matrix, int width, int height, + ColorMap colorRange, Color trasparentColor) { + m = matrix; + description = name; + color = colorRange; + xSize = width; + ySize = height; + trasparencyColor = new int[3]; + trasparencyColor[0] = trasparentColor.getRed(); + trasparencyColor[1] = trasparentColor.getGreen(); + trasparencyColor[2] = trasparentColor.getBlue(); + + img = new BufferedImage(xSize * cellSize, ySize * cellSize, BufferedImage.TYPE_INT_ARGB); + buildBufferImage(); + } + + /** + * Create a double layer drawer using values using values taken from an IntGrid + * matrix and a given IColorMap. It allows to define a trasparency color. + * Every time the drawer has to plot the trasparentColor it stops drawing, + * so the cell of underneath layer becomes visible. + * + * @param name The string describing the layer. + * @param matrix An IntGrid object. + * @param colorRange The IColorMap used to map values to colors. + * @param trasparentColor A color + */ + public LayerIntGridDrawer(String name, IntSpace matrix, + ColorMap colorRange, Color trasparentColor) { + this(name, matrix.getMatrix(), matrix.getXSize(), matrix.getYSize(), + colorRange, trasparentColor); + } + + // Implementing LayerDrawerInterface interface + + /** + * Draw the layer using the given cell length. + * + * @param g The graphic context passed by container. + * @param cellLen The length of a cell in pixels. + */ + public void paint(Graphics g, int cellLen) { + if (trasparencyColor != null) + paintWithTrasparency(g, cellLen); + else + paintWithoutTrasparency(g, cellLen); + } + + private void setCellLenght(int cellLength) { + cellSize = cellLength; + if (trasparencyColor == null) + img = new BufferedImage(xSize * cellSize, ySize * cellSize, BufferedImage.TYPE_INT_RGB); + else + img = new BufferedImage(xSize * cellSize, ySize * cellSize, BufferedImage.TYPE_INT_ARGB); + + buildBufferImage(); + } + + private void paintWithoutTrasparency(Graphics g, int cellLen) { + WritableRaster raster = img.getRaster(); + int[] pixels = new int[3 * cellLen * cellLen]; + + int[] currColor; + int currIndex; + + if (cellSize != cellLen) + setCellLenght(cellLen); + + int k = 0; + for (int j = 0; j < ySize; j++) + for (int i = 0; i < xSize; i++) { + currIndex = color.getColorIndex(m[k]); + if (currIndex != stateBuffer[k]) { + currColor = color.getColorComponents(currIndex); + stateBuffer[k] = currIndex; + int XX = i * cellLen; + int YY = j * cellLen; + + for (int z = 0; z < pixels.length; z += 3) { + pixels[z] = currColor[0]; + pixels[z + 1] = currColor[1]; + pixels[z + 2] = currColor[2]; + } + + raster.setPixels(XX, YY, cellLen, cellLen, pixels); + } + k++; + } + + g.drawImage(img, 0, 0, null); + } + + private void paintWithTrasparency(Graphics g, int cellLen) { + WritableRaster raster = img.getRaster(); + int[] pixels = new int[4 * cellLen * cellLen]; + int[] currColor; + int currIndex; + int alpha = 0; + + if (cellSize != cellLen) + setCellLenght(cellLen); + + int k = 0; + for (int j = 0; j < ySize; j++) + for (int i = 0; i < xSize; i++) { + currIndex = color.getColorIndex(m[k]); + if (currIndex != stateBuffer[k]) { + currColor = color.getColorComponents(currIndex); + stateBuffer[k] = currIndex; + int XX = i * cellLen; + int YY = j * cellLen; + + if (Arrays.equals(currColor, trasparencyColor)) + alpha = 0; + else + alpha = 255; + + for (int z = 0; z < pixels.length; z += 4) { + pixels[z] = currColor[0]; + pixels[z + 1] = currColor[1]; + pixels[z + 2] = currColor[2]; + pixels[z + 3] = alpha; + } + + raster.setPixels(XX, YY, cellLen, cellLen, pixels); + } + k++; + } + + g.drawImage(img, 0, 0, null); + } + + /** + * Return if the layer is currently displayed on the LayeredSurfaceFrame. + * + * @return True if it is currently painted, false otherwise. + */ + public boolean isDisplayed() { + return isDisplayed; + } + + /** + * Decide if layer has to be painted or not. + * + * @param display True if you want the layer to be painted, false otherwise. + */ + public void setDisplay(boolean display) { + isDisplayed = display; + } + + /** + * Return the name of the layer. + * + * @return The name passed to the constructor. + */ + public String getDescription() { + return description; + } + + /** + * Set a manager for mouse events. If not defined, mouse events are + * managed by the class itself. + * + * @param listener A ILayerMouseListener object. + */ + public void setMouseListener(ILayerMouseListener listener) { + mouseListener = listener; + } + + /** + * If a mouse listener has been defined the double-click event, it is passed + * to it, otherwise it is shown a message box with the value contained + * by the clicked cell. + * + * @param atX The x coordinate of the clicked cell. + * @param atY The y coordinate of the clicked cell. + * @return always true if no mouse listener is defined. + * This value is used by caller to know if + * the layer wants to manage the event. + */ + public boolean performDblClickActionAt(int atX, int atY) { + if (mouseListener != null) + return mouseListener.performDblClickActionAt(atX, atY); + + javax.swing.JOptionPane.showMessageDialog(null, + "Value at(" + atX + ", " + atY + "): " + m[atY * xSize + atX], + "Probing " + getDescription(), + javax.swing.JOptionPane.INFORMATION_MESSAGE); + return true; + } + + /** + * If a mouse listener has been defined the right-click event, it is passed + * to it, otherwise it is returned false. + * + * @param atX The x coordinate of the clicked cell. + * @param atY The y coordinate of the clicked cell. + * @return always false if no mouse listener is defined. + * This value is used by caller to know if + * the layer wants to manage the event. + */ + public boolean performRightClickActionAt(int atX, int atY) { + if (mouseListener != null) + return mouseListener.performRightClickActionAt(atX, atY); + + return false; + } + + /** + * If a mouse listener has been defined the mouse dragging event, it is passed + * to it, otherwise it is returned false. + * + * @param fromX The x coordinate of the starting cell. + * @param fromY The y coordinate of the starting cell. + * @param toX The x coordinate of the last dragged cell. + * @param toY The y coordinate of the last dragged cell. + * @return always false if no mouse listener is defined. + * This value is used by caller to know if + * the layer wants to manage the event. + */ + public boolean performMouseMovedFromTo(int fromX, int fromY, int toX, int toY) { + if (mouseListener != null) + return mouseListener.performMouseMovedFromTo(fromX, fromY, toX, toY); + + return false; + } + +} diff --git a/src/main/java/microsim/gui/space/LayerMouseListener.java b/src/main/java/microsim/gui/space/LayerMouseListener.java new file mode 100644 index 00000000..30600048 --- /dev/null +++ b/src/main/java/microsim/gui/space/LayerMouseListener.java @@ -0,0 +1,52 @@ +package microsim.gui.space; + +/** + * A generic implementation of the ILayerMouseListener interface. + * If you want to manage only one or two mouse events, you can extend + * this class, overriding only the useful methods. The methods not overridden + * return always false. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class LayerMouseListener implements ILayerMouseListener { + public boolean performDblClickActionAt(int atX, int atY) { + return false; + } + + public boolean performRightClickActionAt(int atX, int atY) { + return false; + } + + public boolean performMouseMovedFromTo(int fromX, int fromY, int toX, int toY) { + return false; + } +} diff --git a/src/main/java/microsim/gui/space/LayerMultiObjectGridDrawer.java b/src/main/java/microsim/gui/space/LayerMultiObjectGridDrawer.java new file mode 100644 index 00000000..8cdbac93 --- /dev/null +++ b/src/main/java/microsim/gui/space/LayerMultiObjectGridDrawer.java @@ -0,0 +1,262 @@ +package microsim.gui.space; + +import java.awt.Color; +import java.awt.Graphics; +import java.lang.reflect.Field; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import microsim.gui.colormap.ColorMap; +import microsim.space.MultiObjectSpace; + +/** + * It is able to draw objects contained by a MultiObjGrid on a + * LayeredSurfaceFrame.
+ * When on a cell there is at least one object it is represented by a circle. + * The objects are drawn using one given color. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class LayerMultiObjectGridDrawer implements ILayerDrawer { + + private static final Logger log = LogManager.getLogger(LayerMultiObjectGridDrawer.class); + + MultiObjectSpace space; + Color c; + + private ColorMap colorMap; + private String agentProperty; + private boolean isDisplayed = true; + private String description; + + private ILayerMouseListener mouseListener = null; + + /** + * Create a new object drawer based on a given MultiObjGrid object. It plots + * the objects using the given color. + * + * @param name The string describing the layer. + * @param matrix A MultiObjGrid object. + * @param color The default color used to plot objects. + */ + public LayerMultiObjectGridDrawer(String name, MultiObjectSpace matrix, Color color) { + description = name; + space = matrix; + c = color; + } + + /** + * Create a new object drawer based on a given MultiObjGrid object. It plots + * the objects using the color of the first object found on each cell. + * + * @param name The string describing the layer. + * @param matrix A MultiObjGrid object. + */ + public LayerMultiObjectGridDrawer(String name, MultiObjectSpace matrix, String agentProperty, ColorMap map) { + this(name, matrix, null); + this.colorMap = map; + this.agentProperty = agentProperty; + } + + /** + * Return if the layer is currently displayed on the LayeredSurfaceFrame. + * + * @return True if it is currently painted, false otherwise. + */ + public boolean isDisplayed() { + return isDisplayed; + } + + /** + * Decide if layer has to be painted or not. + * + * @param display True if you want the layer to be painted, false otherwise. + */ + public void setDisplay(boolean display) { + isDisplayed = display; + } + + /** + * Return the name of the layer. + * + * @return The name passed to the constructor. + */ + public String getDescription() { + return description; + } + + /** + * Draw the layer using the given cell length. + * + * @param g The graphic context passed by container. + * @param cellLen The length of a cell in pixels. + */ + public void paint(Graphics g, int cellLen) { + if (c != null) + paintWithColor(g, cellLen); + else + try { + paintWithoutColor(g, cellLen); + } catch (SecurityException e) { + log.error(e.getMessage()); + } catch (IllegalArgumentException e) { + log.error(e.getMessage()); + } catch (NoSuchFieldException e) { + log.error(e.getMessage()); + } catch (IllegalAccessException e) { + log.error(e.getMessage()); + } + } + + private void paintWithColor(Graphics g, int cellLen) { + + g.setColor(c); + + for (int i = 0; i < space.getXSize(); i++) + for (int j = 0; j < space.getYSize(); j++) + if (space.countObjectsAt(i, j) > 0) { + int XX = i * cellLen; + int YY = j * cellLen; + g.fillOval(XX, YY, cellLen, cellLen); + } + } + + private Color getColor(Object agent) + throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + Class clazz = agent.getClass(); + Field field = clazz.getField(agentProperty); + field.setAccessible(true); + int index; + if (field.getType().equals(Double.class)) + index = colorMap.getColorIndex(field.getDouble(agent)); + else + index = colorMap.getColorIndex(field.getInt(agent)); + + int[] components = colorMap.getColorComponents(index); + return new Color(components[0], components[1], components[2]); + + } + + private void paintWithoutColor(Graphics g, int cellLen) + throws SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException { + Object[] obj; + Color cl, currentColor = null; + + for (int i = 0; i < space.getXSize(); i++) + for (int j = 0; j < space.getYSize(); j++) + if ((obj = (Object[]) space.get(i, j)) != null) + for (int k = 0; k < obj.length; k++) + if (obj[k] != null) { + cl = getColor(obj[k]); + if (cl != currentColor) { + currentColor = cl; + g.setColor(currentColor); + } + int XX = i * cellLen; + int YY = j * cellLen; + g.fillOval(XX, YY, cellLen, cellLen); + k = obj.length; + } + } + + /** + * Set a manager for mouse events. If not defined, mouse events are + * managed by the class itself. + * + * @param listener A ILayerMouseListener object. + */ + public void setMouseListener(ILayerMouseListener listener) { + mouseListener = listener; + } + + /** + * If a mouse listener has been defined the double-click event, it is passed + * to it, otherwise it is shown a CellObjectChooser that allows the user + * to choose which object to be probed. + * + * @param atX The x coordinate of the clicked cell. + * @param atY The y coordinate of the clicked cell. + * @return always true if no mouse listener is defined. + * This value is used by caller to know if + * the layer wants to manage the event. + */ + public boolean performDblClickActionAt(int atX, int atY) { + if (mouseListener != null) + return mouseListener.performDblClickActionAt(atX, atY); + + if (space.get(atX, atY) == null) + return false; + + Object[] p = (Object[]) space.get(atX, atY); + CellObjectChooser chooser = new CellObjectChooser(p, null, "Objects at " + atX + + ", " + atY, true); + chooser.setVisible(true); + return true; + } + + /** + * If a mouse listener has been defined the right-click event, it is passed + * to it, otherwise it is returned false. + * + * @param atX The x coordinate of the clicked cell. + * @param atY The y coordinate of the clicked cell. + * @return always false if no mouse listener is defined. + * This value is used by caller to know if + * the layer wants to manage the event. + */ + public boolean performRightClickActionAt(int atX, int atY) { + if (mouseListener != null) + return mouseListener.performRightClickActionAt(atX, atY); + + return false; + } + + /** + * If a mouse listener has been defined the mouse dragging event, it is passed + * to it, otherwise it is returned false. + * + * @param fromX The x coordinate of the starting cell. + * @param fromY The y coordinate of the starting cell. + * @param toX The x coordinate of the last dragged cell. + * @param toY The y coordinate of the last dragged cell. + * @return always false if no mouse listener is defined. + * This value is used by caller to know if + * the layer wants to manage the event. + */ + public boolean performMouseMovedFromTo(int fromX, int fromY, int toX, int toY) { + if (mouseListener != null) + return mouseListener.performMouseMovedFromTo(fromX, fromY, toX, toY); + + return false; + } +} diff --git a/src/main/java/microsim/gui/space/LayerObjectGridDrawer.java b/src/main/java/microsim/gui/space/LayerObjectGridDrawer.java new file mode 100644 index 00000000..117a178c --- /dev/null +++ b/src/main/java/microsim/gui/space/LayerObjectGridDrawer.java @@ -0,0 +1,287 @@ +package microsim.gui.space; + +import java.awt.Color; +import java.awt.Graphics; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import microsim.gui.colormap.ColorMap; +import microsim.gui.probe.ProbeFrame; +import microsim.reflection.ReflectionUtils; +import microsim.space.ObjectSpace; +import microsim.statistics.reflectors.DoubleInvoker; +import microsim.statistics.reflectors.IntegerInvoker; + +/** + * It is able to draw objects contained by an ObjGrid on a + * LayeredSurfaceFrame.
+ * An object is represented by a circle. The objects could be drawn using one + * given color or, implementing the IColored interface inside them, each object + * return to the LayerObjGridDrawer which color to use. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class LayerObjectGridDrawer implements ILayerDrawer { + + private static final Logger log = LogManager.getLogger(LayerObjectGridDrawer.class); + + ObjectSpace space; + Color c; + + private ColorMap colorMap; + private boolean isDisplayed = true; + private String description; + + private Object invoker = null; + + private ILayerMouseListener mouseListener = null; + + /** + * Create a new object drawer based on a given Grid object. It plots the + * objects using the given color. NOTICE that the matrix parameter accepts a + * generic Grid, so also classes like IntGrid could be drawn. + * + * @param name + * The string describing the layer. + * @param matrix + * A generic Grid object. + * @param color + * The default color used to plot objects. + */ + public LayerObjectGridDrawer(String name, ObjectSpace matrix, Color color) { + description = name; + space = matrix; + c = color; + } + + /** + * Create a new object drawer based on a given Grid object. It plots the + * objects using the color they return. NOTICE that the matrix parameter + * accepts a generic Grid, so also classes like IntGrid could be drawn. + * + * @param name + * The string describing the layer. + * @param matrix + * A generic Grid object containing IColored objects. + */ + public LayerObjectGridDrawer(String name, ObjectSpace matrix, Class targetClass, String variableName, + boolean isMethod, ColorMap map) { + this(name, matrix, null); + this.colorMap = map; + if (ReflectionUtils.isDoubleSource(targetClass, variableName, isMethod)) + invoker = new DoubleInvoker(targetClass, variableName, isMethod); + else if (ReflectionUtils.isIntSource(targetClass, variableName, isMethod)) + invoker = new IntegerInvoker(targetClass, variableName, isMethod); + else + throw new IllegalArgumentException("Supported field type: double, int"); + } + + // Implementing LayerDrawerInterface interface + + private void paintWithColor(Graphics g, int cellLen) { + g.setColor(c); + + for (int i = 0; i < space.getXSize(); i++) + for (int j = 0; j < space.getYSize(); j++) + if (space.countObjectsAt(i, j) > 0) { + int XX = i * cellLen; + int YY = j * cellLen; + g.fillOval(XX, YY, cellLen, cellLen); + } + } + + private Color getColor(Object agent) throws SecurityException, + NoSuchFieldException, IllegalArgumentException, + IllegalAccessException { + + int level; + if (invoker instanceof DoubleInvoker) + level = (int) ((DoubleInvoker) invoker).getDouble(agent); + else + level = (int) ((IntegerInvoker) invoker).getInt(agent); + + int index = colorMap.getColorIndex(level); + + int[] components = colorMap.getColorComponents(index); + return new Color(components[0], components[1], components[2]); + + } + + private void paintWithoutColor(Graphics g, int cellLen) + throws SecurityException, IllegalArgumentException, + NoSuchFieldException, IllegalAccessException { + Object obj; + Color cl, currentColor = null; + + for (int i = 0; i < space.getXSize(); i++) + for (int j = 0; j < space.getYSize(); j++) + if ((obj = space.get(i, j)) != null) { + cl = getColor(obj); + if (cl != currentColor) { + currentColor = cl; + g.setColor(currentColor); + } + int XX = i * cellLen; + int YY = j * cellLen; + g.fillOval(XX, YY, cellLen, cellLen); + } + } + + /** + * Draw the layer using the given cell length. + * + * @param g + * The graphic context passed by container. + * @param cellLen + * The length of a cell in pixels. + */ + public void paint(Graphics g, int cellLen) { + if (c != null) + paintWithColor(g, cellLen); + else + try { + paintWithoutColor(g, cellLen); + } catch (SecurityException e) { + log.error(e.getMessage()); + } catch (IllegalArgumentException e) { + log.error(e.getMessage()); + } catch (NoSuchFieldException e) { + log.error(e.getMessage()); + } catch (IllegalAccessException e) { + log.error(e.getMessage()); + } + } + + /** + * Return if the layer is currently displayed on the LayeredSurfaceFrame. + * + * @return True if it is currently painted, false otherwise. + */ + public boolean isDisplayed() { + return isDisplayed; + } + + /** + * Decide if layer has to be painted or not. + * + * @param display + * True if you want the layer to be painted, false otherwise. + */ + public void setDisplay(boolean display) { + isDisplayed = display; + } + + /** + * Return the name of the layer. + * + * @return The name passed to the constructor. + */ + public String getDescription() { + return description; + } + + /** + * Set a manager for mouse events. If not defined, mouse events are managed + * by the class itself. + * + * @param listener + * A ILayerMouseListener object. + */ + public void setMouseListener(ILayerMouseListener listener) { + mouseListener = listener; + } + + /** + * If a mouse listener has been defined the double-click event, it is passed + * to it, otherwise it is shown a message box with the value contained by + * the clicked cell. + * + * @param atX + * The x coordinate of the clicked cell. + * @param atY + * The y coordinate of the clicked cell. + * @return always true if no mouse listener is defined. This value is used + * by caller to know if the layer wants to manage the event. + */ + public boolean performDblClickActionAt(int atX, int atY) { + if (mouseListener != null) + return mouseListener.performDblClickActionAt(atX, atY); + + if (space.get(atX, atY) == null) + return false; + + Object p = space.get(atX, atY); + ProbeFrame pf = new ProbeFrame(p, p.toString()); + pf.setVisible(true); + return true; + } + + /** + * If a mouse listener has been defined the right-click event, it is passed + * to it, otherwise it is returned false. + * + * @param atX + * The x coordinate of the clicked cell. + * @param atY + * The y coordinate of the clicked cell. + * @return always false if no mouse listener is defined. This value is used + * by caller to know if the layer wants to manage the event. + */ + public boolean performRightClickActionAt(int atX, int atY) { + if (mouseListener != null) + return mouseListener.performRightClickActionAt(atX, atY); + + return false; + } + + /** + * If a mouse listener has been defined the mouse dragging event, it is + * passed to it, otherwise it is returned false. + * + * @param fromX + * The x coordinate of the starting cell. + * @param fromY + * The y coordinate of the starting cell. + * @param toX + * The x coordinate of the last dragged cell. + * @param toY + * The y coordinate of the last dragged cell. + * @return always false if no mouse listener is defined. This value is used + * by caller to know if the layer wants to manage the event. + */ + public boolean performMouseMovedFromTo(int fromX, int fromY, int toX, + int toY) { + if (mouseListener != null) + return mouseListener + .performMouseMovedFromTo(fromX, fromY, toX, toY); + + return false; + } + +} diff --git a/src/main/java/microsim/gui/space/LayeredSurfaceFrame.java b/src/main/java/microsim/gui/space/LayeredSurfaceFrame.java new file mode 100644 index 00000000..ecbe195f --- /dev/null +++ b/src/main/java/microsim/gui/space/LayeredSurfaceFrame.java @@ -0,0 +1,267 @@ +package microsim.gui.space; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; + +import javax.swing.JInternalFrame; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; + +import microsim.event.CommonEventType; +import microsim.event.EventListener; +import microsim.gui.shell.MicrosimShell; + +/** + * It is the Space Viewer window. It draws grid layers using a list of + * ILayerDrawer objects. See {@code LayeredGridDrawer} classes of this + * library. + * They are wrapper classes for Grid objects of the jas.space.* library and are + * able to plot their contents. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class LayeredSurfaceFrame extends JInternalFrame implements + EventListener { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + private final static int MIN_WIDTH = 10; // 200; + private final static int MIN_HEIGHT = 10; // 100; + + private final static int DEFAULT_CELL_LENGHT = 4; + + private int xSize; + private int ySize; + private int cellLen; + private Dimension screenSize; + + BorderLayout borderLayout1 = new BorderLayout(); + JScrollPane jScrollPane = new JScrollPane(); + LayeredSurfacePanel jLayeredPanel; + + JPopupMenu popupMenu = new JPopupMenu(); + + /** + * @link dependency + * @label open window + */ + /* #LayeredSurfaceProperties lnkLayeredSurfaceProperties; */ + + /** + * Create a new frame with given dimensions and a cell length of 4 pixels. + * + * @param width + * The width of the grid to plot. + * @param height + * The height of the grid to plot. + * @throws IllegalArgumentException if {@code width <= 0 || height <= 0}. + */ + public LayeredSurfaceFrame(int width, int height) { + this(width, height, width, height, DEFAULT_CELL_LENGHT); + } + + /** + * Create a new frame with given dimensions and given cell length. + * + * @param width + * The width of the grid to plot. + * @param height + * The height of the grid to plot. + * @param cellLength + * The lenght of a grid cell in pixels. + * @throws IllegalArgumentException if {@code width <= 0 || height <= 0}. + */ + public LayeredSurfaceFrame(int width, int height, int cellLength) { + this(width, height, width, height, cellLength); + } + + /** + * Create a new frame with given dimensions, given cell length and given + * view-port dimensions. + * + * @param width + * The width of the viewable area in cells. + * @param height + * The height of the viewable area in cells. + * @param cellLength + * The lenght of a grid cell in pixels. + * @param gridWidth + * The real width of the grid to plot. + * @param gridHeight + * The real height of the grid to plot. + * @throws IllegalArgumentException if {@code width <= 0 || height <= 0}. + */ + public LayeredSurfaceFrame(int width, int height, int gridWidth, + int gridHeight, int cellLength) { + + // ImageIcon imageIcon = new ImageIcon( + // LayeredSurfaceFrame.class.getResource("/jas/images/ca.gif")); + + if (width <= 0 || height <= 0) + throw new IllegalArgumentException( + "LayeredSurfaceFrame must be created " + + "with positive width and heigth values."); + + screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + xSize = width; + ySize = height; + cellLen = cellLength; + + jLayeredPanel = new LayeredSurfacePanel(gridWidth, gridHeight, cellLen); + + // setIconImage(imageIcon.getImage()); + + try { + jbInit(); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + private void jbInit() throws Exception { + this.setResizable(true); + this.setTitle("Space viewer"); + setLocation(50, 50); + this.getContentPane().setLayout(borderLayout1); + + jLayeredPanel.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseReleased(MouseEvent e) { + jLayeredPanel_mouseReleased(e); + } + }); + this.getContentPane().add(jScrollPane, BorderLayout.CENTER); + jScrollPane.getViewport().add(jLayeredPanel, null); + + JMenuItem props = new JMenuItem("Properties"); + props.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnProperties_actionPerformed(e); + } + }); + popupMenu.add(props); + // popupMenu.addSeparator(); + + adjustSize(); + } + + private void adjustSize() { + setSize(0, 0); + } + + /** + * Change the current cell length. + * + * @param cellLength + * The new cell length in pixels. + */ + public void setCellLength(int cellLength) { + cellLen = cellLength; + jLayeredPanel.setCellLength(cellLength); + } + + /** + * Add a ILayerDrawer to the layer list. + * + * @param layer + * The ILayerDrawer to be plotted. + */ + public void addLayer(ILayerDrawer layer) { + jLayeredPanel.addLayer(layer); + } + + /** Repaint the plot area. */ + public void update() { + jLayeredPanel.repaint(); + } + + private void jBtnProperties_actionPerformed(ActionEvent e) { + LayeredSurfaceProperties dlg = new LayeredSurfaceProperties( + MicrosimShell.currentShell, "Space viewer properties", cellLen, + jLayeredPanel.getLayers()); + dlg.setVisible(true); + + if (!dlg.modified) + return; + + if (dlg.newCellSize > 0) + setCellLength(dlg.newCellSize); + + adjustSize(); + this.setVisible(true); + } + + /** + * Update the window size according to the parameters passed to the + * constructor. + * + * @param x + * It is ignored. The width is computed automatically. + * @param y + * It is ignored. The height is computed automatically. + */ + public void setSize(int x, int y) { + int width = cellLen * xSize + 10; + int height = cellLen * ySize + 28; // BUTTON_PANEL_HEIGHT; + + if (width > screenSize.getWidth()) + width = (int) screenSize.getWidth(); + if (height > screenSize.getHeight()) + height = (int) screenSize.getHeight(); + + if (width < MIN_WIDTH) + width = MIN_WIDTH; + if (height < MIN_HEIGHT) + height = MIN_HEIGHT; + + super.setSize(width, height); + } + + /** + * React to system events. + * + * @param type + * Reacts to the Sim.EVENT_UPDATE event repainting the plot area. + */ + public void onEvent(Enum type) { + if (type == CommonEventType.Update) { + update(); + } + } + + void jLayeredPanel_mouseReleased(MouseEvent e) { + if (e.isPopupTrigger() || e.getButton() == 3) + popupMenu.show(e.getComponent(), e.getX(), e.getY()); + } + +} diff --git a/src/main/java/microsim/gui/space/LayeredSurfacePanel.java b/src/main/java/microsim/gui/space/LayeredSurfacePanel.java new file mode 100644 index 00000000..f9ccff6b --- /dev/null +++ b/src/main/java/microsim/gui/space/LayeredSurfacePanel.java @@ -0,0 +1,252 @@ +package microsim.gui.space; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.JPanel; +import java.util.List; +import java.util.ArrayList; + +/** + * Not of interest for users. It is the panel drawing the + * {@code LayerDrawer} objects added to the LayeredSurfaceFrame. + * It manages mouse events, too. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class LayeredSurfacePanel extends JPanel { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + private List m_layers; + private int xSize; + private int ySize; + private int cellLen; + + private int virtualWidth, virtualHeigth; + + private Color background; + + // Used for dragging + private int lastX, lastY; + + /** + * @link dependency + * @stereotype use + */ + /* #ILayerDrawer lnkILayerDrawer; */ + + /** @link dependency */ + /* #CellObjectChooser lnkCellObjectChooser; */ + + /** + * Create a panel with dimensions of (100, 100) and a cell length of 4 pixels. + */ + public LayeredSurfacePanel() { + this(100, 100, 4); + } + + /** + * Create a panel with given dimensions and given cell length. + * + * @param width The width of the grid to plot. + * @param height The height of the grid to plot. + * @param cellLength The lenght of a grid cell in pixels. + */ + public LayeredSurfacePanel(int width, int height, int cellLength) { + xSize = width; + ySize = height; + cellLen = cellLength; + setVirtualDimensions(); + m_layers = new ArrayList(); + try { + jbInit(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + void jbInit() throws Exception { + this.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() { + public void mouseDragged(MouseEvent e) { + this_mouseDragged(e); + } + }); + this.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(MouseEvent e) { + this_mouseClicked(e); + } + + public void mousePressed(MouseEvent e) { + this_mousePressed(e); + } + + public void mouseReleased(MouseEvent e) { + this_mouseReleased(e); + } + }); + } + + /** + * Return the current background color. + * + * @return The current background color. Null if color has been not set. + */ + public Color getBackgroundColor() { + return background; + } + + /** + * Set the current background color. + * + * @param color The current background color. + */ + public void setBackgroundColor(Color color) { + background = color; + } + + private void setVirtualDimensions() { + virtualWidth = xSize * cellLen; + virtualHeigth = ySize * cellLen; + + this.setSize(virtualWidth, virtualHeigth); + this.setPreferredSize(new Dimension(virtualWidth, virtualHeigth)); + } + + /** + * Change the size of the grid. + * + * @param width The width of the grid to plot. + * @param height The height of the grid to plot. + */ + public void setVirtualSize(int width, int height) { + xSize = width; + ySize = height; + setVirtualDimensions(); + } + + /** + * Add a ILayerDrawer to the layer list. + * + * @param layer The ILayerDrawer to be plotted. + */ + public void addLayer(ILayerDrawer layer) { + m_layers.add(layer); + } + + /** + * Return the list of current added layers. + * + * @return An ArrayList of ILayerDrawer objects. + */ + public List getLayers() { + return m_layers; + } + + /** + * Change the current cell length. + * + * @param cellLength The new cell length in pixels. + */ + public void setCellLength(int cellLength) { + cellLen = cellLength; + setVirtualDimensions(); + } + + /** + * Draw the panel. + * + * @param g The graphic context passed by container. + */ + public void paintComponent(Graphics g) { + super.paintComponent(g); + + if (background != null) { + g.setColor(background); + g.fillRect(0, 0, virtualWidth, virtualHeigth); + } + + ILayerDrawer lay; + for (int i = 0; i < m_layers.size(); i++) { + lay = (ILayerDrawer) m_layers.get(i); + if (lay.isDisplayed()) + lay.paint(g, cellLen); + } + + } + + private void this_mouseClicked(MouseEvent e) { + ILayerDrawer lay; + + if (e.getClickCount() != 2) + return; + + int x = (int) (e.getX() / cellLen); + int y = (int) (e.getY() / cellLen); + + for (int i = m_layers.size() - 1; i >= 0; i--) { + lay = (ILayerDrawer) m_layers.get(i); + if (lay.isDisplayed()) + if (lay.performDblClickActionAt(x, y)) + return; + } + + } + + private void this_mousePressed(MouseEvent e) { + lastX = (int) (e.getX() / cellLen); + lastY = (int) (e.getY() / cellLen); + } + + private void this_mouseDragged(MouseEvent e) { + } + + private void this_mouseReleased(MouseEvent e) { + ILayerDrawer lay; + if (lastX < 0 || lastX > virtualWidth || + lastY < 0 || lastY > virtualHeigth) + return; + + int x = (int) (e.getX() / cellLen); + int y = (int) (e.getY() / cellLen); + + for (int i = m_layers.size() - 1; i >= 0; i--) { + lay = (ILayerDrawer) m_layers.get(i); + if (lay.isDisplayed()) + if (lay.performMouseMovedFromTo(lastX, lastY, x, y)) + return; + } + + } + +} diff --git a/src/main/java/microsim/gui/space/LayeredSurfaceProperties.java b/src/main/java/microsim/gui/space/LayeredSurfaceProperties.java new file mode 100644 index 00000000..0571c196 --- /dev/null +++ b/src/main/java/microsim/gui/space/LayeredSurfaceProperties.java @@ -0,0 +1,166 @@ +package microsim.gui.space; + +import java.awt.*; +import javax.swing.*; +import java.awt.event.*; +import javax.swing.border.*; + +/** + * Not of interest for users. It is the properties frame called + * by the LayeredSurfaceFrame when the uses presses the 'Properties' + * button. + * + *

+ * Title: JAS + *

+ *

+ * Description: Java Agent-based Simulation library + *

+ *

+ * Copyright (C) 2002 Michele Sonnessa + *

+ * + * This library is free software; you can redistribute it and/or modify it under + * the terms + * of the GNU Lesser General Public License as published by the Free Software + * Foundation; + * either version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this + * library; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * @author Michele Sonnessa + *

+ */ +public class LayeredSurfaceProperties extends JDialog { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + public boolean modified; + public int newCellSize; + + private static final int MAX_CELL_LENGTH = 8; + + private java.util.List displayLayers; + + JPanel jpanel = new JPanel(); + JPanel jSizePanel = new JPanel(); + JPanel jMainPanel = new JPanel(); + JPanel jButtonPanel = new JPanel(); + JButton jBtnCancel = new JButton(); + JButton jBtnOK = new JButton(); + JComboBox jCmbSize = new JComboBox(); + JLabel jLabel1 = new JLabel(); + JLabel jLabel2 = new JLabel(); + TitledBorder titledBorder1; + GridLayout gridLayout1 = new GridLayout(); + + public LayeredSurfaceProperties(Frame frame, String title, + int cellSize, java.util.List layers) { + super(frame, title, true); + + displayLayers = layers; + + try { + jbInit(); + pack(); + } catch (Exception ex) { + ex.printStackTrace(); + } + + for (int i = 1; i <= MAX_CELL_LENGTH; i++) + jCmbSize.addItem("" + i); + jCmbSize.setSelectedIndex(cellSize - 1); + + java.util.Iterator it = displayLayers.iterator(); + while (it.hasNext()) { + ILayerDrawer lay = (ILayerDrawer) it.next(); + JCheckBox jc = new JCheckBox(lay.getDescription()); + jc.setSelected(lay.isDisplayed()); + jMainPanel.add(jc); + } + + this.setSize(300, 300); + modified = false; + } + + public LayeredSurfaceProperties() { + this(null, "", 1, null); + } + + void jbInit() throws Exception { + titledBorder1 = new TitledBorder(""); + jButtonPanel.setBorder(BorderFactory.createEtchedBorder()); + jButtonPanel.setMinimumSize(new Dimension(85, 40)); + jButtonPanel.setPreferredSize(new Dimension(85, 40)); + jBtnCancel.setText("Cancel"); + jBtnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnCancel_actionPerformed(e); + } + }); + jBtnOK.setText("OK"); + jBtnOK.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + jBtnOK_actionPerformed(e); + } + }); + jCmbSize.setPreferredSize(new Dimension(100, 22)); + jLabel1.setText("Cell width"); + jMainPanel.setLayout(gridLayout1); + jLabel2.setText("Current layers:"); + jpanel.setBorder(BorderFactory.createEtchedBorder()); + jSizePanel.setBorder(BorderFactory.createEtchedBorder()); + gridLayout1.setColumns(1); + gridLayout1.setRows(10); + getContentPane().add(jpanel, BorderLayout.CENTER); + this.getContentPane().add(jSizePanel, BorderLayout.NORTH); + this.getContentPane().add(jButtonPanel, BorderLayout.SOUTH); + jButtonPanel.add(jBtnCancel, null); + jButtonPanel.add(jBtnOK, null); + jpanel.add(jMainPanel, null); + jMainPanel.add(jLabel2, null); + jSizePanel.add(jLabel1, null); + jSizePanel.add(jCmbSize, null); + + this.setSize(new Dimension(300, 300)); + this.setLocation(200, 200); + } + + void jBtnCancel_actionPerformed(ActionEvent e) { + dispose(); + } + + void jBtnOK_actionPerformed(ActionEvent e) { + ILayerDrawer lay; + for (int i = 0; i < displayLayers.size(); i++) { + lay = (ILayerDrawer) displayLayers.get(i); + lay.setDisplay(getStatusCheck(lay.getDescription())); + } + + modified = true; + newCellSize = jCmbSize.getSelectedIndex() + 1; + dispose(); + } + + private boolean getStatusCheck(String checkName) { + Component[] cs = jMainPanel.getComponents(); + for (int i = 0; i < cs.length; i++) + if (cs[i] instanceof JCheckBox) + if (((JCheckBox) cs[i]).getText().equals(checkName)) + return ((JCheckBox) cs[i]).isSelected(); + + return false; + } +} diff --git a/src/main/java/microsim/gui/utils/CustomFileFilter.java b/src/main/java/microsim/gui/utils/CustomFileFilter.java new file mode 100644 index 00000000..0b8dd0f9 --- /dev/null +++ b/src/main/java/microsim/gui/utils/CustomFileFilter.java @@ -0,0 +1,42 @@ +package microsim.gui.utils; + +import javax.swing.filechooser.*; +import java.io.File; +import java.util.ArrayList; + +public class CustomFileFilter extends FileFilter { + String extension, description; + ArrayList additionalExt = null; + + public CustomFileFilter(String extension, String description) { + this.extension = extension; + this.description = description; + } + + public void addExtension(String extension) { + if (additionalExt == null) + additionalExt = new ArrayList(); + + additionalExt.add(extension); + } + + public boolean accept(File f) { + if (additionalExt == null) + return f.isDirectory() || + f.getName().endsWith(extension); + + boolean acceptable = f.isDirectory() || + f.getName().endsWith(extension); + + for (int i = 0; i < additionalExt.size(); i++) + if (f.getName().endsWith(additionalExt.get(i).toString())) + return true; + + return acceptable; + } + + public String getDescription() { + return description; + } + +} diff --git a/src/main/java/microsim/gui/utils/ImageGenerator.java b/src/main/java/microsim/gui/utils/ImageGenerator.java new file mode 100644 index 00000000..0c8583d4 --- /dev/null +++ b/src/main/java/microsim/gui/utils/ImageGenerator.java @@ -0,0 +1,185 @@ +package microsim.gui.utils; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.Dimension; +import java.awt.event.*; +import javax.imageio.ImageIO; + +import javax.swing.filechooser.FileFilter; +import java.io.*; +import javax.swing.*; + +import org.apache.batik.svggen.SVGGraphics2D; +import org.apache.batik.dom.GenericDOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.DOMImplementation; + +public class ImageGenerator { + + public static SVGGraphics2D getSVGDocument() { + // Get a DOMImplementation + DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation(); + + // Create an instance of org.w3c.dom.Document + Document document = domImpl.createDocument(null, "svg", null); + + // Create an instance of the SVG Generator + SVGGraphics2D svgGenerator = new SVGGraphics2D(document); + + return svgGenerator; + } + + public static String getOutput(SVGGraphics2D generator) { + ByteArrayOutputStream stream = null; + try { + stream = new ByteArrayOutputStream(); + OutputStreamWriter out = new OutputStreamWriter(stream, "UTF-8"); + // Writer out = new OutputStreamWriter(System.out, "UTF-8"); + generator.stream(out, true); + } catch (UnsupportedEncodingException ue) { + } catch (IOException e) { + System.err.println("Error in SVG generation: " + e.getMessage()); + } + + return stream.toString(); + } + + public static void save(SVGGraphics2D generator, String fileName) { + try { + BufferedWriter out = new BufferedWriter(new FileWriter(fileName)); + // Writer out = new OutputStreamWriter(System.out, "UTF-8"); + generator.stream(out, true); + out.close(); + } catch (IOException e) { + System.err.println("Error in SVG generation: " + e.getMessage()); + } + } + + public static String generate(JPanel panel) { + SVGGraphics2D svgGenerator = getSVGDocument(); + svgGenerator.setSVGCanvasSize(panel.getSize()); + + panel.paint(svgGenerator); + + return getOutput((SVGGraphics2D) svgGenerator); + } + + public static String generate(JFrame frame) { + SVGGraphics2D svgGenerator = getSVGDocument(); + svgGenerator.setSVGCanvasSize(frame.getSize()); + + frame.paint(svgGenerator); + + return getOutput(svgGenerator); + } + + public static void save(JPanel panel, String fileName) { + SVGGraphics2D svgGenerator = getSVGDocument(); + + panel.paint(svgGenerator); + + save(svgGenerator, fileName); + } + + public static void save(JFrame frame, String fileName) { + SVGGraphics2D svgGenerator = getSVGDocument(); + + frame.paint(svgGenerator); + + save(svgGenerator, fileName); + } + + public static BufferedImage toImage(JPanel panel) { + // Create a Buffered Image + Dimension d = panel.getSize(); + BufferedImage img = new BufferedImage(d.width + 10, + d.height + 10, BufferedImage.TYPE_INT_RGB); + Graphics2D graphics = img.createGraphics(); + graphics.translate(5, 5); + panel.paint(graphics); + + return img; + } + + public static String[] supportedFormats() { + return javax.imageio.ImageIO.getWriterFormatNames(); + } + + public static void saveImage(JPanel panel, String fileName, String format) { + try { + BufferedImage img = toImage(panel); + File f = new File(fileName); + ImageIO.write(img, format, f); + } catch (IOException e) { + System.err.println("Error saving image: " + e.getMessage()); + } + } + + public static JMenu getExportMenu(JPanel panel) { + Action action; + JMenu exportMenu = new JMenu("Export"); + final JPanel expPanel = panel; + + String[] formats = ImageIO.getWriterFormatNames(); + + // SVG + action = new AbstractAction("SVG") { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + public void actionPerformed(ActionEvent e) { + File file = saveDialog("SVG"); + if (file != null) + save(expPanel, file.toString()); + } + }; + exportMenu.add(new JMenuItem(action)); + + for (int i = 0; i < formats.length; i++) { + final String format = formats[i]; + if (format.toUpperCase().equals(format)) { + action = new AbstractAction(format) { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + public void actionPerformed(ActionEvent e) { + File file = saveDialog(format); + if (file != null) + try { + BufferedImage im = toImage(expPanel); + ImageIO.write(im, format, file); + } catch (IOException ex) { + System.err.println("Error saving image: " + ex.getMessage()); + } + } + }; + exportMenu.add(new JMenuItem(action)); + } + } + + return exportMenu; + } + + public static File saveDialog(String format) { + File f = new File("."); + JFileChooser jfc = new JFileChooser(f); + + FileFilter ff = new CustomFileFilter(format, format + " file"); + jfc.setFileFilter(ff); + + int result = jfc.showSaveDialog(null); + if (result == JFileChooser.CANCEL_OPTION) + return null; + + File selectedFile = jfc.getSelectedFile(); + if (!(selectedFile.toString().endsWith(format))) + selectedFile = new File(selectedFile.getAbsolutePath() + "." + format); + return selectedFile; + } + +} diff --git a/src/main/resources/microsim/gui/icons/Save16.gif b/src/main/resources/microsim/gui/icons/Save16.gif new file mode 100644 index 00000000..954f1acc Binary files /dev/null and b/src/main/resources/microsim/gui/icons/Save16.gif differ diff --git a/src/main/resources/microsim/gui/icons/clear16.gif b/src/main/resources/microsim/gui/icons/clear16.gif new file mode 100644 index 00000000..25583265 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/clear16.gif differ diff --git a/src/main/resources/microsim/gui/icons/console.gif b/src/main/resources/microsim/gui/icons/console.gif new file mode 100644 index 00000000..3bcafea8 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/console.gif differ diff --git a/src/main/resources/microsim/gui/icons/db.gif b/src/main/resources/microsim/gui/icons/db.gif new file mode 100644 index 00000000..8a3389e8 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/db.gif differ diff --git a/src/main/resources/microsim/gui/icons/engine16.gif b/src/main/resources/microsim/gui/icons/engine16.gif new file mode 100644 index 00000000..8dc8ffe2 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/engine16.gif differ diff --git a/src/main/resources/microsim/gui/icons/graph.gif b/src/main/resources/microsim/gui/icons/graph.gif new file mode 100644 index 00000000..edbff73f Binary files /dev/null and b/src/main/resources/microsim/gui/icons/graph.gif differ diff --git a/src/main/resources/microsim/gui/icons/logo_2.png b/src/main/resources/microsim/gui/icons/logo_2.png new file mode 100644 index 00000000..6fcc03ad Binary files /dev/null and b/src/main/resources/microsim/gui/icons/logo_2.png differ diff --git a/src/main/resources/microsim/gui/icons/msIco.gif b/src/main/resources/microsim/gui/icons/msIco.gif new file mode 100644 index 00000000..1239cc50 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/msIco.gif differ diff --git a/src/main/resources/microsim/gui/icons/quit.gif b/src/main/resources/microsim/gui/icons/quit.gif new file mode 100644 index 00000000..34643b8b Binary files /dev/null and b/src/main/resources/microsim/gui/icons/quit.gif differ diff --git a/src/main/resources/microsim/gui/icons/simulation_build.gif b/src/main/resources/microsim/gui/icons/simulation_build.gif new file mode 100644 index 00000000..fb5175f5 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/simulation_build.gif differ diff --git a/src/main/resources/microsim/gui/icons/simulation_pause.gif b/src/main/resources/microsim/gui/icons/simulation_pause.gif new file mode 100644 index 00000000..457893c1 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/simulation_pause.gif differ diff --git a/src/main/resources/microsim/gui/icons/simulation_play.gif b/src/main/resources/microsim/gui/icons/simulation_play.gif new file mode 100644 index 00000000..2088548a Binary files /dev/null and b/src/main/resources/microsim/gui/icons/simulation_play.gif differ diff --git a/src/main/resources/microsim/gui/icons/simulation_refresh.gif b/src/main/resources/microsim/gui/icons/simulation_refresh.gif new file mode 100644 index 00000000..9eb88fa4 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/simulation_refresh.gif differ diff --git a/src/main/resources/microsim/gui/icons/simulation_step.gif b/src/main/resources/microsim/gui/icons/simulation_step.gif new file mode 100644 index 00000000..6081cba1 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/simulation_step.gif differ diff --git a/src/main/resources/microsim/gui/icons/simulation_stop.gif b/src/main/resources/microsim/gui/icons/simulation_stop.gif new file mode 100644 index 00000000..d47876f6 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/simulation_stop.gif differ diff --git a/src/main/resources/microsim/gui/icons/simulation_update_params.png b/src/main/resources/microsim/gui/icons/simulation_update_params.png new file mode 100644 index 00000000..32390f9a Binary files /dev/null and b/src/main/resources/microsim/gui/icons/simulation_update_params.png differ diff --git a/src/main/resources/microsim/gui/icons/tree.gif b/src/main/resources/microsim/gui/icons/tree.gif new file mode 100644 index 00000000..11a99130 Binary files /dev/null and b/src/main/resources/microsim/gui/icons/tree.gif differ diff --git a/src/main/resources/microsim/gui/icons/view.gif b/src/main/resources/microsim/gui/icons/view.gif new file mode 100644 index 00000000..793b189d Binary files /dev/null and b/src/main/resources/microsim/gui/icons/view.gif differ