/* * 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 */