/*
Copyright 2006 by Daniel Kuebrich
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
// Class RuleUI
package sim.app.lsystem;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import sim.util.gui.*;
// This class becomes the Rules pane of the Console
public class RuleUI extends JPanel
{
private static final long serialVersionUID = -8650952120231540392L;
// components
JButton buttonGo = new JButton("Calculate");
JButton buttonCancel = new JButton("Cancel");
JButton buttonSave = new JButton("Save");
JButton buttonLoad = new JButton("Load");
JButton buttonHelp = new JButton("Help");
// put the table in its own scroll pane, as the console is not too big
JTable ruleTable = new JTable(20,2);
JScrollPane scrollPane = new JScrollPane(ruleTable);
JProgressBar calcProgress = new JProgressBar(0,100);
JTextField seedField = new JTextField("F-F-F-F", 10);
JTextField stepField = new JTextField("3", 3);
// help panel (see very end of the init() function for elaboration on this)
JPanel helpPanel = new JPanel();
// references to sim with ui, sim state
LSystemWithUI lsui;
LSystem ls;
// so that we can update the draw settings tab also
DrawUI dui;
// for calculation thread
int steps=0; // expansions
Runnable calcRunnable;
Thread calcThread;
Object lock = new Object();
boolean stop = false;
// returns the frame that should be used as the parent frame for dialogs
public Frame getFrame()
{
Component c = this;
while(c.getParent() != null)
c = c.getParent();
return (Frame)c;
}
// this is currently used only by buttonGo
// it takes the currently entered data for rules and sends it to the LSystem.LSystemData intstance
void getRulesFromUI()
{
// set l-system parameters
// seed
ls.l.seed = seedField.getText();
LSystemData.setVector(ls.l.code, ls.l.seed);
// expansions
ls.l.expansions = Integer.valueOf(stepField.getText()).intValue();
// erase old rules
ls.l.rules.clear();
// build new rule list
for(int r=0; r<ruleTable.getRowCount(); r++)
{
// if both not null
if(ruleTable.getValueAt(r,0) != null && ruleTable.getValueAt(r,1) != null)
// and both not zero length
if(((String)(ruleTable.getValueAt(r,0))).length() > 0 && ((String)(ruleTable.getValueAt(r,0))).length() > 0)
ls.l.rules.add( new Rule( (byte)(((String)(ruleTable.getValueAt(r,0))).
substring(0,1).charAt(0)), (String)ruleTable.
getValueAt(r,1)) );
}
// set # of expansions
steps = Integer.valueOf(stepField.getText()).intValue();
}
// constructor
public RuleUI(LSystemWithUI nLsui, DrawUI nDui)
{
lsui = nLsui;
ls = (LSystem)lsui.state;
dui = nDui;
try
{
init();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void init()
{
// This runnable calculates the expansions of the L-system when the "Calculate"
// button is pushed. Because it runs in a separate thread, it can conveniently
// be cancelled, and updates a JProgressBar to show that it is still thinking.
calcRunnable = new Runnable()
{
public void run()
{
int h=0; //number of expansions
int p=0; //position in original code
int r=0; //rule check index
boolean ruleApplied = false; // has a rule been applied to this symbol yet
// Speed... Make a new ByteList and copy into there
// instead of inserting into the old one and shifting the elements over...
// Also, I have not written an insert function, so this is a double bonus.
ByteList newCode;
newCode = new ByteList(ls.l.code.b.length);
// main expanion loop
while(true)
{
// stop if external stop requested
// this occurrs when the cancel button is pressed
synchronized(lock)
{
if(stop)
break;
}
// stop if enough expansions have been completed
if(h >= steps)
break;
// else keep expanding
ruleApplied = false; // reset this
for(r=0; r<ls.l.rules.size(); r++)
{
if(ls.l.code.b[p] == (((Rule)ls.l.rules.get(r)).pattern)) // replace!
{
newCode.addAll(((Rule)ls.l.rules.get(r)).replace);
ruleApplied = true;
// dont try to expand extra rules, that would be trouble
break;
}
}
if(!ruleApplied) // if no rule was found for this item
{
newCode.add(ls.l.code.b[p]);
}
p++; // increment p to go to the next
// Cycle the progress bar to show thinking!
// You're probably thinking.. "Hey.. this is such a waste of time, why
// didn't you just use that nifty 'barber pole' auto-scrolling effect?"
// Well, that's good stuff, and it's called "Indeterminate mode". However,
// this capability seems to only exist in >= 1.4...
if(p%100 == 0)
{
SwingUtilities.invokeLater(
new Runnable()
{
public void run()
{
int i = calcProgress.getValue();
if(i < 100)
i++;
else
i = 0;
calcProgress.setValue(i);
}
}
);
}
// an expansion has been completed
// hurray
if(p >= ls.l.code.length)
{
p = 0;
h++;
ls.l.code = newCode;
newCode = new ByteList(ls.l.code.length);
}
}
// end main expansion loop
// on successful end, enable calculate and disable cancel buttons
SwingUtilities.invokeLater(
new Runnable()
{
public void run()
{
buttonGo.setEnabled(true);
buttonCancel.setEnabled(false);
calcProgress.setValue(0);
calcProgress.setString("Done!");
}
}
);
}// end run
};
// buttonGo calculates the expansions of the rules from the given seed
buttonGo.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
getRulesFromUI();
// now we will begin..
calcProgress.setString("Calculating...");
// expand
// in a separate thread
stop = false;
calcThread = new Thread(calcRunnable);
calcThread.start();
// juggle buttons
buttonCancel.setEnabled(true);
buttonGo.setEnabled(false);
}
});
// buttonCancel stops an expansion being processed (started by buttonGo "Calculate")
buttonCancel.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
synchronized(lock)
{
// let the thread stop on its own...
stop = true;
}
// but wait for it
try
{
calcThread.join();
}
catch (Exception ex)
{
ex.printStackTrace();
}
// reset buttons
calcProgress.setValue(0);
calcProgress.setString("Cancelled");
buttonCancel.setEnabled(false);
buttonGo.setEnabled(true);
}
});
// buttonSave saves the current seed, rules, draw settings, and expansions
// Saves the data after a Calculate has been executed.. so if you
// Enter data A
// then Calculate
// then Enter data B
// then save, you will be saving data A.
// so be careful!
buttonSave.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
JOptionPane.showMessageDialog( getFrame(),
"<html>IF you have changed the settings since the last time you calculated the L-system,<br>"+
"the L-system you save will be the one last calculated--not the current data!</html>" );
// show save dialog
FileDialog fd = new FileDialog(getFrame(),
"Save Current L-System Settings As", FileDialog.SAVE);
fd.setFile("Untitled.lss");
fd.setVisible(true);;
// on cancel, return
if(fd.getFile() == null)
return;
// else do the thing
File outputFile = new File(fd.getDirectory(), fd.getFile());
FileOutputStream outputFileStream = new FileOutputStream(outputFile);
java.util.zip.GZIPOutputStream g = new java.util.zip.GZIPOutputStream(
new BufferedOutputStream(outputFileStream));
ObjectOutputStream out = new ObjectOutputStream(g);
out.writeObject(ls.l);
// now need to do a little dance with the GZIPOutputStream to write
// this stuff out correctly -- see sim.engine.SimState.writeToCheckpoint(OutputStream)
// for more info
out.flush();
g.finish();
g.flush();
out.close();
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
});
// buttonLoad loads the file's seed, rule, and expansions
buttonLoad.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
// show load dialog
FileDialog fd = new FileDialog(getFrame(),
"Open L-System Settings File (.lss)", FileDialog.LOAD);
fd.setFile("*.lss");
fd.setVisible(true);;
// on cancel, return
if(fd.getFile() == null)
return;
File inputFile = new File(fd.getDirectory(), fd.getFile());
FileInputStream inputFileStream = new FileInputStream(inputFile);
ObjectInputStream in = new ObjectInputStream(
new java.util.zip.GZIPInputStream(new BufferedInputStream(inputFileStream)));
ls.l = (LSystemData)in.readObject();
in.close();
// change UI fields to reflect newly loaded settings
// seed
seedField.setText(ls.l.seed);
// # expansions
stepField.setText(String.valueOf(ls.l.expansions));
// line size
dui.distField.setText(String.valueOf(ls.l.segsize));
// angle... has been stored in radians, so un-radian it
dui.angleField.setText(String.valueOf(ls.l.angle*180/Math.PI));
// x, y
// --- unneccessary now that Display2D options do this
//dui.xField.setText(String.valueOf(ls.l.x));
//dui.yField.setText(String.valueOf(ls.l.y));
// rules
// first clear table
for(int t=0; t<ruleTable.getRowCount(); t++)
{
ruleTable.setValueAt(null, t, 0);
ruleTable.setValueAt(null, t, 1);
}
// now set new stuff
for(int t=0; t<ls.l.rules.size(); t++)
{
ruleTable.setValueAt(String.valueOf((char)((Rule)(ls.l.rules.get(t))).pattern), t, 0);
ruleTable.setValueAt(LSystemData.fromVector(((Rule)(ls.l.rules.get(t))).replace), t, 1);
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
});
// buttonHelp displays symbol help
buttonHelp.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// this makes it free-floating rather than modal
// so you can move to the side and consult the dialog while using the console
JFrame help = new JFrame();
help.getContentPane().setLayout( new BorderLayout() );
help.getContentPane().add(helpPanel, BorderLayout.CENTER);
help.setSize(400,300);
help.setVisible(true);
}
});
///// OK
// now build the actual UI
this.setLayout(new BorderLayout());
// so much panel juggling
// looks like this:
/*
All JPanels use BorderLayout.
N = NORTH, C = CENTER, S = SOUTH
+-------JPanel this (ruleUI)-----------------+
| +-------JPanel "mid"---------------------+ |
| | +--------JPanel "top"----------------+ | |
| | | +--------LabelledList "list"-----+ | | |
| | |N| Seed =============== | | | |
| |N| | Expanions =============== | | | |
|N| | +--------------------------------+ | | |
| | |C Box( Calculate Cancel ) | | |
| | |S (____progressbar__________) | | |
| | +------------------------------------+ | |
| |C Box( Save Load ) | |
| +----------------------------------------+ |
| |
|C JScrollPane(JTable rules) |
+--------------------------------------------+
So that's the overview of the next 50 lines or so
*/
JPanel top = new JPanel();
JPanel mid = new JPanel();
top.setLayout(new BorderLayout());
mid.setLayout(new BorderLayout());
// List of L-system parameters.. what is a LabelledList?
// sim.display.LabelledList is a convenient way to draw lists of the format
// text component
// text component
LabelledList list = new LabelledList()
{
private static final long serialVersionUID = -2153709747861890863L;
Insets insets = new Insets(5, 5, 5, 5);
public Insets getInsets()
{
return insets;
}
};
seedField.setText(ls.l.seed);
list.addLabelled("Seed", seedField);
list.addLabelled("Expansions", stepField);
// add everything so far (in list)
top.add(list, BorderLayout.NORTH);
// box with calculate and cancel buttons
Box b = new Box(BoxLayout.X_AXIS)
{
private static final long serialVersionUID = -869949739122977643L;
Insets insets = new Insets(5, 5, 5, 5);
public Insets getInsets()
{
return insets;
}
};
b.add(buttonGo);
b.add(buttonCancel);
buttonCancel.setEnabled(false);
b.add(Box.createGlue());
top.add(b, BorderLayout.CENTER);
top.add(calcProgress, BorderLayout.SOUTH);
calcProgress.setStringPainted(true);
calcProgress.setString("Press Calculate");
b = new Box(BoxLayout.X_AXIS)
{
private static final long serialVersionUID = -2124038237393174259L;
Insets insets = new Insets(5, 5, 5, 5);
public Insets getInsets()
{
return insets;
}
};
b.add(buttonSave);
b.add(buttonLoad);
b.add(buttonHelp);
b.add(Box.createGlue());
mid.add(top, BorderLayout.NORTH);
mid.add(b, BorderLayout.CENTER);
this.add(mid, BorderLayout.NORTH);
// allows table to have named headers
class NamedTableModel extends DefaultTableModel
{
private static final long serialVersionUID = -6638838065039609876L;
NamedTableModel(int rows, int cols)
{
super(rows, cols);
}
public String getColumnName( int i )
{
if(i == 0)
return "Symbol";
else if(i == 1)
return "Replacement";
else
return "Error.";
}
};
ruleTable.setModel(new NamedTableModel(20,2));
// rules table setup
// same defaults as in LSystemWithUI.java
seedField.setText("F");
ruleTable.setValueAt("F", 0, 0);
ruleTable.setValueAt("F[+F]F[-F]F", 0, 1);
this.add(scrollPane, BorderLayout.CENTER);
// Make the Help popup!
// This is executed on a click of buttonHelp.. but we've got it set up now.
LabelledList list2 = new LabelledList();
helpPanel.setLayout(new BorderLayout());
list2.addLabelled("Symbols", new JLabel(""));
list2.addLabelled("Uppercase (A-Z)", new JLabel("Draw forward Distance units"));
list2.addLabelled("Lowercase (a-z)", new JLabel("Move forward Distance units (no draw)"));
list2.addLabelled("-", new JLabel("Turn right by angle degrees"));
list2.addLabelled("+", new JLabel("Turn left by angle degrees"));
list2.addLabelled("[", new JLabel("Push position, angle"));
list2.addLabelled("]", new JLabel("Pop position, angle"));
list2.addLabelled("", new JLabel(""));
list2.addLabelled("Save: ", new JLabel("Saves the rules, seed, draw settings, and "));
list2.addLabelled("", new JLabel("calculated expansions of the "));
list2.addLabelled("", new JLabel("Last calculated L-system."));
list2.addLabelled("Load: ", new JLabel("Loads saved L-system settings."));
helpPanel.add(list2, BorderLayout.CENTER);
}
}