/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.controls;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.Iterator;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import org.opensourcephysics.display.Hidable;
import org.opensourcephysics.display.OSPFrame;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.numerics.DoubleArray;
import org.opensourcephysics.numerics.IntegerArray;
import org.opensourcephysics.tools.ToolsRes;
/**
* A Control that shows its parameters in a JTable.
* Custom buttons can be added.
*
* @author Wolfgang Christian
* @version 1.0
*/
public class OSPControl extends ControlFrame implements PropertyChangeListener, MainFrame {
OSPControlTable table = new OSPControlTable(new XMLControlElement());
JScrollPane controlScrollPane = new JScrollPane(table);
JTextArea messageTextArea;
JLabel clearLabel, messageLabel, inputLabel;
JSplitPane splitPane;
JMenuItem translateItem;
static final Color PANEL_BACKGROUND = javax.swing.UIManager.getColor("Panel.background"); //$NON-NLS-1$
/**
* Constructs an OSPControl.
*
* @param _model
*/
public OSPControl(Object _model) {
super(ControlsRes.getString("OSPControl.Default_Title")); //$NON-NLS-1$
model = _model;
if(model!=null) {
// added by D Brown 2006-09-10
// modified by D Brown 2007-10-17
if(OSPRuntime.getTranslator()!=null) {
OSPRuntime.getTranslator().associate(this, model.getClass());
}
String name = model.getClass().getName();
setTitle(name.substring(1+name.lastIndexOf("."))+ControlsRes.getString("OSPControl.Controller")); //$NON-NLS-1$ //$NON-NLS-2$
}
Font labelFont = new Font("Dialog", Font.BOLD, 12); //$NON-NLS-1$
inputLabel = new JLabel(ControlsRes.getString("OSPControl.Input_Parameters"), SwingConstants.CENTER); //$NON-NLS-1$
inputLabel.setFont(labelFont);
messageTextArea = new JTextArea(5, 5) {
public void paintComponent(Graphics g) {
if(OSPRuntime.antiAliasText) {
Graphics2D g2 = (Graphics2D) g;
RenderingHints rh = g2.getRenderingHints();
rh.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
rh.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
super.paintComponent(g);
}
};
JScrollPane messageScrollPane = new JScrollPane(messageTextArea);
// contains a view of the control
JPanel topPanel = new JPanel(new BorderLayout());
topPanel.add(inputLabel, BorderLayout.NORTH);
topPanel.add(controlScrollPane, BorderLayout.CENTER);
buttonPanel.setVisible(true);
topPanel.add(buttonPanel, BorderLayout.SOUTH); // buttons are added using addButton method.
// clear panel acts like a button to clear the message area
JPanel clearPanel = new JPanel(new BorderLayout());
clearPanel.addMouseListener(new ClearMouseAdapter());
clearLabel = new JLabel(ControlsRes.getString("OSPControl.Clear")); //$NON-NLS-1$
clearLabel.setFont(new Font(clearLabel.getFont().getFamily(), Font.PLAIN, 9));
clearLabel.setForeground(Color.black);
clearPanel.add(clearLabel, BorderLayout.WEST);
// contains the messages
JPanel bottomPanel = new JPanel(new BorderLayout());
messageLabel = new JLabel(ControlsRes.getString("OSPControl.Messages"), SwingConstants.CENTER); //$NON-NLS-1$
messageLabel.setFont(labelFont);
bottomPanel.add(messageLabel, BorderLayout.NORTH);
bottomPanel.add(messageScrollPane, BorderLayout.CENTER);
bottomPanel.add(clearPanel, BorderLayout.SOUTH);
Container cp = getContentPane();
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topPanel, bottomPanel);
splitPane.setOneTouchExpandable(true);
cp.add(splitPane, BorderLayout.CENTER);
messageTextArea.setEditable(false);
controlScrollPane.setPreferredSize(new Dimension(350, 200));
controlScrollPane.setMinimumSize(new Dimension(0, 50));
messageScrollPane.setPreferredSize(new Dimension(350, 75));
if((OSPRuntime.getTranslator()!=null)&&(model!=null)) {
OSPRuntime.getTranslator().associate(table, model.getClass());
}
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
setLocation((d.width-getSize().width)/2, (d.height-getSize().height)/2); // center the frame
init();
ToolsRes.addPropertyChangeListener("locale", this); //$NON-NLS-1$
}
/**
* Gets this frame. Implementation of MainFrame interface.
* @return OSPFrame
*/
public OSPFrame getMainFrame() {
return this;
}
/**
* Adds a Display menu to the menu bar. Overrides OSPFrame method.
*
* @return the display menu
*/
protected JMenu loadDisplayMenu() {
JMenuBar menuBar = getJMenuBar();
if(menuBar==null) {
return null;
}
JMenu menu = super.loadDisplayMenu();
translateItem = new JMenuItem();
translateItem.setText(ControlsRes.getString("OSPControl.Translate")); //$NON-NLS-1$
// changed by D Brown 2007-10-17
if(OSPRuntime.getTranslator()!=null) {
translateItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
OSPRuntime.getTranslator().showProperties(model.getClass());
if(OSPRuntime.getTranslator() instanceof Hidable) {
((Hidable) OSPRuntime.getTranslator()).setKeepHidden(false);
}
OSPRuntime.getTranslator().setVisible(true);
}
});
translateItem.setEnabled(OSPRuntime.isAuthorMode());
languageMenu.add(translateItem, 0);
}
// changed by D Brown 2006-09-10
if(languageMenu.getItemCount()>1) {
languageMenu.insertSeparator(1);
}
return menu;
}
/**
* Refreshes the user interface in response to display changes such as Language.
*/
protected void refreshGUI() {
if(messageLabel==null) {
return;
}
super.refreshGUI();
messageLabel.setText(ControlsRes.getString("OSPControl.Messages")); //$NON-NLS-1$
clearLabel.setText(ControlsRes.getString("OSPControl.Clear")); //$NON-NLS-1$
inputLabel.setText(ControlsRes.getString("OSPControl.Input_Parameters")); //$NON-NLS-1$
table.refresh();
}
/**
* Listens for property change events.
*
* @param e the property change event
*/
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if(name.equals("translation")||name.equals("locale")) {//$NON-NLS-1$ //$NON-NLS-2$
refreshGUI();
// forward event to other listeners
} else {
firePropertyChange(e.getPropertyName(), e.getOldValue(), e.getNewValue());
}
}
/**
* Initializes this control after all objects have been created.
*
* Override this method and change the default close operation if this control is used with an applet.
*/
protected void init() {
splitPane.setDividerLocation(-1);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public Object getModel() {
return model;
}
/**
* Sets the location of the divider between the control table and the message panel.
* @param loc int
*/
public void setDividerLocation(int loc) {
splitPane.setDividerLocation(loc);
}
/**
* Sets the editable property of the given parameter so that it can not be changed from within the GUI.
*
* @param parameter String
* @param editable boolean
*/
public void setEditable(String parameter, boolean editable) {
table.setEditable(parameter, editable);
}
/**
* Locks the control's interface. Values sent to the control will not
* update the display until the control is unlocked.
*
* @param lock boolean
*/
public void setLockValues(boolean lock) {
table.setLockValues(true);
}
/**
* Creates a string representation of the control parameters.
*
* @return the control parameters
*/
public String toString() {
return table.toString();
}
/**
* Adds a parameter to the input display.
*
* @param par the parameter name
* @param val the initial parameter value
*/
public void setValue(String par, Object val) {
table.setValue(par, val);
}
/**
* Adds an initial boolean value of a parameter to the input display.
*
* @param par the parameter name
* @param val the initial parameter value
*/
public void setValue(String par, boolean val) {
table.setValue(par, val);
}
/**
* Adds an initial value of a parameter to the input display.
*
* @param par the parameter name
* @param val the initial parameter value
*/
public void setValue(String par, double val) {
table.setValue(par, Double.toString(val));
}
/**
* Adds an initial value of a parameter to the input display.
*
* @param par the parameter name
* @param val the initial parameter value
*/
public void setValue(String par, int val) {
table.setValue(par, Integer.toString(val));
}
/**
* Removes a parameter from the table.
*
* @param par the parameter name
*/
public void removeParameter(String par) {
table.setValue(par, null);
}
/**
* Reads a parameter value from the input display.
*
* @param par
* @return double the value of of the parameter
*/
public double getDouble(String par) {
return table.getDouble(par);
}
/**
* Reads a parameter value from the input display.
*
* @param par
* @return int the value of of the parameter
*/
public int getInt(String par) {
return table.getInt(par);
}
/**
* Gets the object with the specified property name.
* Throws an UnsupportedOperationException if the named object has not been stored.
*
* @param par
* @return the object
*/
public Object getObject(String par) throws UnsupportedOperationException {
return table.getObject(par);
}
/**
* Reads a parameter value from the input display.
*
* @param par the parameter name
* @return String the value of of the parameter
*/
public String getString(String par) {
return table.getString(par);
}
/**
* Reads a parameter value from the input display.
*
* @param par the parameter name
* @return the value of of the parameter
*/
public boolean getBoolean(String par) {
return table.getBoolean(par);
}
/**
* Reads the current property names.
*
* @return the property names
*/
public Collection<String> getPropertyNames() {
return table.getPropertyNames();
}
/**
* Adds a custom button to the control's frame.
*
* @param methodName the name of the method; the method has no parameters
* @param text the button's text label
* @return the custom button
*/
public JButton addButton(String methodName, String text) {
return addButton(methodName, text, null, model);
}
/**
* Adds a custom button to the control's frame.
*
* @param methodName the name of the method; the method has no parameters
* @param text the button's text label
* @param toolTipText the button's tool tip text
* @return the custom button
*/
public JButton addButton(String methodName, String text, String toolTipText) {
return addButton(methodName, text, toolTipText, model);
}
/**
* Adds a ControlTableListener that invokes method in the control's model.
* The method in the model is invoked with the table's variable name passed as a
* parameter.
*
* @param methodName the name of the method; the method has no parameters
*/
public void addControlListener(String methodName) {
addControlListener(methodName, model);
}
/**
* Adds a ControlTableListener that invokes method in the given object.
* The method in the target is invoked with the table's variable name passed as a
* parameter.
*
* @param methodName the name of the method; the method has no parameters
* @param target the target for the method
*/
public void addControlListener(String methodName, final Object target) {
Class<?>[] parameters = new Class[] {String.class};
try {
final java.lang.reflect.Method m = target.getClass().getMethod(methodName, parameters);
table.tableModel.addTableModelListener(new TableModelListener() {
public void tableChanged(TableModelEvent e) {
if((e.getType()!=TableModelEvent.UPDATE)||(e.getColumn()!=1)||(e.getFirstRow()<0)) {
return;
}
String name = table.getValueAt(e.getFirstRow(), 0).toString();
Object[] args = {name};
try {
m.invoke(target, args);
} catch(IllegalAccessException iae) {}
catch(java.lang.reflect.InvocationTargetException ite) {}
}
});
} catch(NoSuchMethodException nsme) {
System.err.println("The method "+methodName+"() does not exist."); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* Prints a line of text in the message area.
*
* @param s
*/
public void println(String s) {
print(s+"\n"); //$NON-NLS-1$
}
/**
* Prints a blank line in the message area.
*/
public void println() {
print("\n"); //$NON-NLS-1$
}
/**
* Prints text in the message area.
*
* @param s
*/
public void print(final String s) {
if(SwingUtilities.isEventDispatchThread()||Thread.currentThread().getName().equals("main")) { //$NON-NLS-1$
messageTextArea.append(s);
return;
}
Runnable doLater = new Runnable() {
public void run() {
messageTextArea.append(s);
}
};
// Update from the event queue.
java.awt.EventQueue.invokeLater(doLater);
}
/**
* Remove all text from the message area.
*/
public void clearMessages() {
if(SwingUtilities.isEventDispatchThread()||Thread.currentThread().getName().equals("main")) { //$NON-NLS-1$
messageTextArea.setText(""); //$NON-NLS-1$
return;
}
Runnable doLater = new Runnable() {
public void run() {
messageTextArea.setText(""); //$NON-NLS-1$
}
};
// Update from the event queue.
java.awt.EventQueue.invokeLater(doLater);
}
/**
* Remove all text from the data input area.
*/
public void clearValues() {
table.clearValues();
}
/**
* A signal that a method has completed. A message is printed in the message area.
*
* @param message
*/
public void calculationDone(String message) {
// not implemented
if(message!=null) {
println(message);
}
}
class ClearMouseAdapter extends java.awt.event.MouseAdapter {
/**
* Method mousePressed
*
* @param evt
*/
public void mousePressed(java.awt.event.MouseEvent evt) {
clearMessages();
}
/**
* Method mouseEntered
*
* @param evt
*/
public void mouseEntered(java.awt.event.MouseEvent evt) {
clearLabel.setFont(new Font(clearLabel.getFont().getFamily(), Font.BOLD, 10));
clearLabel.setText(ControlsRes.getString("OSPControl.Click_to_clear_message")); //$NON-NLS-1$
}
/**
* Method mouseExited
*
* @param evt
*/
public void mouseExited(java.awt.event.MouseEvent evt) {
clearLabel.setFont(new Font(clearLabel.getFont().getFamily(), Font.PLAIN, 9));
clearLabel.setText(ControlsRes.getString("OSPControl.Clear")); //$NON-NLS-1$
}
}
/**
* Returns an XML.ObjectLoader to save and load data for this object.
*
* @return the object loader
*/
public static XML.ObjectLoader getLoader() {
return new OSPControlLoader();
}
/**
* A class to save and load data for OSPControls.
*/
static class OSPControlLoader implements XML.ObjectLoader {
/**
* Saves object data to an XMLControl.
*
* @param prefsXMLControl the control to save to
* @param obj the object to save
*/
public void saveObject(XMLControl xmlControl, Object obj) {
OSPControl ospControl = (OSPControl) obj;
saveControlProperites(xmlControl, ospControl);
// save the model if the control is the top level
if(xmlControl.getLevel()==0) {
xmlControl.setValue("model", ospControl.model); //$NON-NLS-1$
}
}
protected void saveControlProperites(XMLControl xmlControl, OSPControl ospControl) {
// save the parameters
Iterator<String> it = ospControl.getPropertyNames().iterator();
while(it.hasNext()) {
String name = it.next();
Object val = ospControl.getObject(name);
if(val.getClass()==DoubleArray.class) {
xmlControl.setValue(name, ((DoubleArray) val).getArray());
} else if(val.getClass()==IntegerArray.class) {
xmlControl.setValue(name, ((IntegerArray) val).getArray());
} else if(val.getClass()==Boolean.class) {
xmlControl.setValue(name, ((Boolean) val).booleanValue());
} else if(val.getClass()==Double.class) {
xmlControl.setValue(name, ((Double) val).doubleValue());
} else if(val.getClass()==Integer.class) {
xmlControl.setValue(name, ((Integer) val).intValue());
} else if(val.getClass()==Character.class) {
xmlControl.setValue(name, ((Character) val).toString());
} else if(val.getClass().isArray()) {
xmlControl.setValue(name, val);
} else {
xmlControl.setValue(name, val);
}
// xmlControl.setValue(name, val.toString());
}
}
/**
* Creates an object using data from an XMLControl.
*
* @param control the control
* @return the newly created object
*/
public Object createObject(XMLControl control) {
return new OSPControl(null);
}
/**
* Loads an object with data from an XMLControl.
*
* @param control the control
* @param obj the object
* @return the loaded object
*/
public Object loadObject(XMLControl control, Object obj) {
OSPControl cf = (OSPControl) obj;
// iterate over properties and add them to table model
Iterator<String> it = control.getPropertyNames().iterator();
cf.table.setLockValues(true);
while(it.hasNext()) {
String name = it.next();
// skip "model" object properties
if(name.equals("model")&&control.getPropertyType(name).equals("object")) { //$NON-NLS-1$ //$NON-NLS-2$
XMLControl child = control.getChildControl("model"); //$NON-NLS-1$
cf.model = child.loadObject(cf.model);
continue;
}
if((cf.getObject(name) instanceof OSPCombo)&&control.getPropertyType(name).equals("string")) { //$NON-NLS-1$
OSPCombo combo = (OSPCombo) cf.getObject(name); // keep the combo but select the correct item
String itemName = control.getString(name);
String[] items = combo.items;
for(int i = 0, n = items.length; i<n; i++) {
if(itemName.equals(items[i])) {
combo.selected = i;
break;
}
}
cf.setValue(name, combo);
} else if(control.getPropertyType(name).equals("string")) { //$NON-NLS-1$
cf.setValue(name, control.getString(name));
} else if(control.getPropertyType(name).equals("int")) { //$NON-NLS-1$
cf.setValue(name, control.getInt(name));
} else if(control.getPropertyType(name).equals("double")) { //$NON-NLS-1$
cf.setValue(name, control.getDouble(name));
} else if(control.getPropertyType(name).equals("boolean")) { //$NON-NLS-1$
cf.setValue(name, control.getBoolean(name));
} else {
cf.setValue(name, control.getObject(name));
}
}
cf.table.setLockValues(false);
// XMLControl child = control.getChildControl("model"); //$NON-NLS-1$
// if(child!=null) {
// cf.model = child.loadObject(cf.model);
// }
return obj;
}
}
/**
* Creates an OSP control and establishes communication between the control and the model.
*
* Custom buttons are usually added to this control to invoke actions in the model.
*
* @param model Object
* @return AnimationControl
*/
public static OSPControl createApp(Object model) {
OSPControl control = new OSPControl(model);
control.setSize(300, 300);
control.setVisible(true);
return control;
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/