package bsearch.app;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
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;
import bsearch.algorithms.SearchParameterException;
import bsearch.evaluation.ResultListener;
import bsearch.evaluation.SearchManager;
import bsearch.nlogolink.ModelRunResult;
import bsearch.nlogolink.ModelRunner.ModelRunnerException;
import bsearch.representations.Chromosome;
import bsearch.util.GeneralUtils;
public class GUIProgressDialog extends JDialog implements PropertyChangeListener, ActionListener, WindowListener {
private static final long serialVersionUID = 1L;
private JProgressBar progressBar;
private JLabel labelMessage;
private JLabel labelTimeRemaining;
private JTextPane infoTextPane;
private JButton buttonCancel;
private SearchTask task;
private long taskStartTime;
JFreeChart fitnessPlot;
private XYSeriesCollection fitnessPlotDataset;
private boolean done = false;
public GUIProgressDialog(Frame parent) {
//super("BehaviorSearch - *running* "); //TODO: show name of search in the title bar
super(parent,true);
this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
this.addWindowListener(this);
progressBar = new JProgressBar(0, 100);
progressBar.setValue(0);
progressBar.setStringPainted(true);
labelMessage = new JLabel("Performing search 0000 out of 0000");
labelMessage.setFont(new Font("Arial", Font.BOLD, 12));
labelTimeRemaining = new JLabel("Elapsed: ? Remaining: ?");
labelTimeRemaining.setFont(new Font("Arial", Font.PLAIN, 12));
infoTextPane = new JTextPane();
infoTextPane.setEditable(false);
infoTextPane.setBorder(BorderFactory.createEmptyBorder(30,5,10,12));
infoTextPane.setContentType("text/html");
infoTextPane.setText("<B><I>Search result status</I></B><BR>");
buttonCancel = new JButton("Cancel");
buttonCancel.addActionListener(this);
fitnessPlotDataset = new XYSeriesCollection();
fitnessPlot = ChartFactory.createXYLineChart("Search Progress",
"# of model runs", "Fitness", fitnessPlotDataset,
PlotOrientation.VERTICAL, true, false, false);
final XYPlot plot = fitnessPlot.getXYPlot();
plot.setBackgroundPaint(Color.lightGray);
final XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
renderer.setBaseShapesVisible(false);
//renderer.setAutoPopulateSeriesShape(false); // don't use different shapes for each series...
//renderer.setBaseShape(new java.awt.geom.Ellipse2D.Double(-1,-1,3,3)); // smaller datapoint size...
renderer.setBaseLegendTextFont(new Font("Arial",Font.PLAIN, 10));
plot.setRenderer(renderer);
ChartPanel cPanel = new ChartPanel(fitnessPlot);
cPanel.setPreferredSize(new Dimension(500,400));
JPanel panelSouth = new JPanel();
panelSouth.setLayout(new BorderLayout());
panelSouth.add(labelMessage, BorderLayout.WEST);
panelSouth.add(progressBar, BorderLayout.CENTER);
panelSouth.add(labelTimeRemaining, BorderLayout.EAST);
panelSouth.add(buttonCancel, BorderLayout.SOUTH);
JPanel panelEast = new JPanel();
panelEast.setLayout(new BorderLayout());
panelEast.add(new JScrollPane(infoTextPane), BorderLayout.CENTER);
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(panelEast, BorderLayout.EAST);
panel.add(cPanel, BorderLayout.CENTER);
panel.add(panelSouth,BorderLayout.SOUTH);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
this.getContentPane().setLayout(new BorderLayout());
this.getContentPane().add(panel, BorderLayout.CENTER);
this.pack();
}
public void startSearchTask(SearchProtocol protocol, BehaviorSearch.RunOptions runOptions)
{
if (protocol.useBestChecking())
{
ValueAxis rangeAxis = fitnessPlot.getXYPlot().getRangeAxis();
rangeAxis.setLabel(rangeAxis.getLabel() + " (rechecked)");
}
labelMessage.setText("Search 0 of " + runOptions.numSearches);
progressBar.setIndeterminate(true);
progressBar.setMaximum(protocol.evaluationLimit);
taskStartTime = System.currentTimeMillis();
task = new SearchTask(protocol, runOptions);
task.addPropertyChangeListener(this);
task.execute();
}
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equals(evt.getPropertyName()))
{
if (evt.getNewValue().equals(SwingWorker.StateValue.DONE))
{
try {
if (task.isCancelled() )
{
javax.swing.JOptionPane.showMessageDialog(this, "You canceled the search. \nPartial results may have been saved to output files.", "Canceled", JOptionPane.WARNING_MESSAGE);
}
else if (task.fatalException != null)
{
throw task.fatalException;
}
}
catch (ModelRunnerException e) {
e.printStackTrace();
BehaviorSearchGUI.handleError("Error running the model:\n" + e.getMessage(), this.getParent());
} catch (SearchParameterException e) {
e.printStackTrace();
BehaviorSearchGUI.handleError("Error setting search method parameters:\n" + e.getMessage(), this.getParent());
}
catch (IOException e) {
BehaviorSearchGUI.handleError("Error reading or writing files:\n" + e, this.getParent());
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
BehaviorSearchGUI.handleError("Error: " + e, this.getParent());
} catch (Throwable e) {
e.printStackTrace();
BehaviorSearchGUI.handleError("Serious Error: " + e, this.getParent());
}
done = true;
buttonCancel.setText("Done");
}
}
}
public void actionPerformed(ActionEvent arg0) {
if (arg0.getSource().equals(buttonCancel))
{
doCancel();
}
}
private void doCancel()
{
if (done)
{
setVisible(false);
}
else
{
progressBar.setIndeterminate(false);
task.cancel(true);
}
}
class SearchTask extends SwingWorker<Void, Void> implements ResultListener
{
private SearchProtocol protocol;
private BehaviorSearch.RunOptions runOptions;
private Throwable fatalException = null;
public SearchTask(SearchProtocol protocol, BehaviorSearch.RunOptions runOptions)
{
this.protocol = protocol;
this.runOptions = runOptions;
}
public void updateGUIForNextSearch(final int searchNum)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
labelMessage.setText("Performing search " + (searchNum - runOptions.firstSearchNumber + 1) + " of " + runOptions.numSearches + ": ");
progressBar.setValue(0);
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) fitnessPlot.getXYPlot().getRenderer();
if (searchNum > runOptions.firstSearchNumber)
{
renderer.setSeriesStroke(searchNum - runOptions.firstSearchNumber - 1, new BasicStroke(1.0f));
}
renderer.setSeriesStroke(searchNum - runOptions.firstSearchNumber, new BasicStroke(3.0f));
}
});
}
public void updateGUIWhenFinished()
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
labelMessage.setText("Finished search " + runOptions.numSearches + " of " + runOptions.numSearches + ": ");
progressBar.setValue(progressBar.getMaximum());
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) fitnessPlot.getXYPlot().getRenderer();
renderer.setSeriesStroke(runOptions.numSearches - 1, new BasicStroke(1.0f));
}
});
}
@Override
public Void doInBackground() {
try {
List<ResultListener> listeners = new ArrayList<ResultListener>();
listeners.add(this);
BehaviorSearch.runWithOptions(runOptions, protocol, listeners);
updateGUIWhenFinished();
} catch (Throwable e) {
fatalException = e; // handle it later, back in the regular Swing event thread
}
return null;
}
public void modelRunOccurred(SearchManager manager, Chromosome point, ModelRunResult result ) {
long currentTime = System.currentTimeMillis();
long elapsed = currentTime-taskStartTime;
String elapsedStr = GeneralUtils.formatTimeNicely(elapsed);
int searchesCompleted = (manager.getSearchIDNumber() - runOptions.firstSearchNumber);
int totalSearches = runOptions.numSearches;
double runsCompleted = manager.getEvaluationCount() + manager.getAuxilliaryEvaluationCount();
double totalRuns = protocol.evaluationLimit + manager.getAuxilliaryEvaluationCount();
double searchProgress = (searchesCompleted + runsCompleted / totalRuns) / totalSearches;
long remaining = (long) (elapsed / searchProgress - elapsed); // in milliseconds
String remainingStr = GeneralUtils.formatTimeNicely(remaining);
labelTimeRemaining.setText(" (" + elapsedStr + " elapsed - " + remainingStr + " remaining)");
}
public void fitnessComputed(SearchManager manager, Chromosome point, double fitness) {
final int searchNumber = manager.getSearchIDNumber();
final double bestFitnessSoFar = protocol.useBestChecking() ? manager.getCurrentBestFitnessCheckedEstimate() : manager.getCurrentBestFitness();
final int evaluationCount = manager.getEvaluationCount();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressBar.setIndeterminate(false);
progressBar.setValue(evaluationCount);
// enlarge the x-axis range if necessary, to show progress
ValueAxis axis = fitnessPlot.getXYPlot().getDomainAxis();
if (axis.getRange().getUpperBound() < evaluationCount + 1)
{
axis.setRange(0, evaluationCount + 1);
}
if (searchNumber - runOptions.firstSearchNumber + 1 > fitnessPlotDataset.getSeriesCount())
{
XYSeries series = new XYSeries("Search " + searchNumber);
fitnessPlotDataset.addSeries(series);
}
XYSeries series = fitnessPlotDataset.getSeries(searchNumber - runOptions.firstSearchNumber);
int itemCount = series.getItemCount();
if (itemCount == 0 || evaluationCount > series.getX(itemCount - 1).intValue()
|| (bestFitnessSoFar != series.getY(itemCount - 1).doubleValue()))
{
// This is a preventive measure to not bog down real-time graphing in JFreeChart:
// if the fitness is on a plateau, we don't need a million XY data points to show a flat line
// we can use just 2 points (one at the beginning of the plateau, and one at the end)
if (itemCount > 1
&& series.getY(itemCount - 1).doubleValue() == bestFitnessSoFar
&& series.getY(itemCount - 2).doubleValue() == bestFitnessSoFar)
{
series.remove(itemCount - 1);
}
series.addOrUpdate(evaluationCount, bestFitnessSoFar);
}
}
});
}
private void updateInfoText(String title, Chromosome c, double fitness, double checkedFitness)
{
String bestText = GeneralUtils.getParamSettingsTextHTML(c.getParamSettings());
StringBuilder sb = new StringBuilder();
sb.append("<B><I>"+title+"</I></B>");
sb.append("<BR><BR><B>Best found so far:</B><BR>");
sb.append(bestText);
sb.append("<BR><B>Fitness</B>=");
sb.append(String.format("%10.6g",fitness));
sb.append("<BR>");
if (protocol.useBestChecking())
{
sb.append("<B>(re-checked)</B>=");
sb.append(String.format("%10.6g",checkedFitness));
sb.append("<BR>");
}
infoTextPane.setText(sb.toString());
}
public void newBestFound(SearchManager manager) {
updateInfoText("In Search #" + manager.getSearchIDNumber() + ":", manager.getCurrentBest(), manager.getCurrentBestFitness(), manager.getCurrentBestFitnessCheckedEstimate());
}
public void initListener(bsearch.space.SearchSpace space) {
// do nothing
}
public void searchStarting(SearchManager archive) {
updateGUIForNextSearch(archive.getSearchIDNumber());
}
Chromosome overallBest = null;
double overallBestFitness;
double overallBestFitnessChecked;
public void searchFinished(SearchManager manager) {
double bestFitness = manager.getCurrentBestFitness();
if (overallBest == null || manager.fitnessStrictlyBetter(bestFitness, overallBestFitness))
{
overallBest = manager.getCurrentBest();
overallBestFitness = bestFitness;
if (protocol.useBestChecking())
{
overallBestFitnessChecked = manager.getCurrentBestFitnessCheckedEstimate();
}
}
}
public void allSearchesFinished() {
updateInfoText("From all searches:", overallBest, overallBestFitness, overallBestFitnessChecked);
}
public void searchesAborted() {
// do nothing
}
}
public void windowClosing(WindowEvent arg0) {
doCancel();
}
public void windowActivated(WindowEvent arg0) {
}
public void windowClosed(WindowEvent arg0) {
}
public void windowDeactivated(WindowEvent arg0) {
}
public void windowDeiconified(WindowEvent arg0) {
}
public void windowIconified(WindowEvent arg0) {
}
public void windowOpened(WindowEvent arg0) {
}
}