/* * 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.tools; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; 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.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.StyleContext; import javax.swing.text.StyledDocument; import javax.swing.undo.UndoManager; import javax.swing.undo.UndoableEdit; import javax.swing.undo.UndoableEditSupport; import org.opensourcephysics.controls.OSPLog; import org.opensourcephysics.display.OSPRuntime; /** * This is a JPanel for managing Functions and supporting Parameters. * * @author Douglas Brown */ public class FunctionPanel extends JPanel implements PropertyChangeListener { // instance fields protected FunctionTool functionTool; protected ParamEditor paramEditor; protected FunctionEditor functionEditor; protected Container box; protected JTextPane instructions; private JButton undoButton; private JButton redoButton; private UndoableEditSupport undoSupport; protected UndoManager undoManager; private int varBegin, varEnd; protected JTextField tableEditorField; protected String prevName, description; private Icon icon; /** * Constructor FunctionPanel * @param editor */ public FunctionPanel(FunctionEditor editor) { super(new BorderLayout()); functionEditor = editor; editor.functionPanel = this; createGUI(); refreshGUI(); } /** * Gets the ParamEditor. * * @return the param editor */ public ParamEditor getParamEditor() { return paramEditor; } /** * Gets the function editor. * * @return the function editor */ public FunctionEditor getFunctionEditor() { return functionEditor; } /** * Gets the function table. * * @return the table */ public FunctionEditor.Table getFunctionTable() { return functionEditor.getTable(); } /** * Gets the parameter table. * * @return the table */ public FunctionEditor.Table getParamTable() { return paramEditor.getTable(); } /** * Gets an appropriate label for the FunctionTool dropdown. * * @return a label string */ public String getLabel() { return ToolsRes.getString("FunctionPanel.Label"); //$NON-NLS-1$ } /** * Gets the display name for the FunctionTool dropdown. By default, this * returns the name of this panel. * * @return the display name */ public String getDisplayName() { return getName(); } /** * Override getPreferredSize(). * * @return the preferred size */ public Dimension getPreferredSize() { Dimension dim = super.getPreferredSize(); dim.width = paramEditor.buttonPanel.getPreferredSize().width; return dim; } /** * Adds names to the forbidden set. * * @param names the names */ protected void addForbiddenNames(String[] names) { for(int i = 0; i<names.length; i++) { functionEditor.forbiddenNames.add(names[i]); if(paramEditor!=null) { paramEditor.forbiddenNames.add(names[i]); } } } /** * Listens for property changes "edit" and "function" * * @param e the event */ public void propertyChange(PropertyChangeEvent e) { if(e.getPropertyName().equals("edit")) { //$NON-NLS-1$ // Parameter or Function has been edited if((e.getNewValue() instanceof UndoableEdit)) { // post undo edit undoSupport.postEdit((UndoableEdit) e.getNewValue()); } refreshFunctions(); refreshGUI(); if (functionTool!=null && functionEditor.getObjects().size()>0) { String functionName = (String) e.getOldValue(); String prevName = null; if(e.getNewValue() instanceof FunctionEditor.DefaultEdit) { FunctionEditor.DefaultEdit edit = (FunctionEditor.DefaultEdit) e.getNewValue(); if(edit.editType==FunctionEditor.NAME_EDIT) { prevName = edit.undoObj.toString(); } } else if (e.getNewValue() instanceof String) { prevName = e.getNewValue().toString(); } functionTool.firePropertyChange("function", prevName, functionName); //$NON-NLS-1$ } } else if(e.getPropertyName().equals("function")) { //$NON-NLS-1$ // function has been added or removed refreshFunctions(); refreshGUI(); if(functionTool!=null) { functionTool.refreshGUI(); functionTool.firePropertyChange("function", null, null); //$NON-NLS-1$ } } else if (e.getPropertyName().equals("description") && functionTool!=null) { //$NON-NLS-1$ functionTool.firePropertyChange("description", null, null); //$NON-NLS-1$ } } /** * Clears the selection. */ protected void clearSelection() { getFunctionTable().clearSelection(); getParamTable().clearSelection(); refreshInstructions(null, false, -1); } /** * Creates the GUI. */ protected void createGUI() { // create textpane and color styles for instructions instructions = new JTextPane() { 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); } }; instructions.setEditable(false); instructions.setOpaque(false); instructions.setFocusable(false); instructions.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2)); StyledDocument doc = instructions.getStyledDocument(); Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE); StyleConstants.setFontFamily(def, "SansSerif"); //$NON-NLS-1$ Style blue = doc.addStyle("blue", def); //$NON-NLS-1$ StyleConstants.setBold(blue, false); StyleConstants.setForeground(blue, Color.blue); Style red = doc.addStyle("red", blue); //$NON-NLS-1$ StyleConstants.setBold(red, true); StyleConstants.setForeground(red, Color.red); instructions.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { if(varEnd==0) { return; } instructions.setCaretPosition(varBegin); instructions.moveCaretPosition(varEnd); tableEditorField.replaceSelection(instructions.getSelectedText()); tableEditorField.setBackground(Color.yellow); } public void mouseExited(MouseEvent e) { if(!hasCircularErrors()&&!hasInvalidExpressions()) { StyledDocument doc = instructions.getStyledDocument(); Style blue = doc.getStyle("blue"); //$NON-NLS-1$ doc.setCharacterAttributes(0, instructions.getText().length(), blue, false); varBegin = varEnd = 0; } } }); instructions.addMouseMotionListener(new MouseMotionAdapter() { public void mouseMoved(MouseEvent e) { varBegin = varEnd = 0; // select and highlight the variable under mouse String text = instructions.getText(); // first separate the instructions from the variables int startVars = text.indexOf(": "); //$NON-NLS-1$ if(startVars==-1) { return; } startVars += 2; String vars = text.substring(startVars); StyledDocument doc = instructions.getStyledDocument(); Style blue = doc.getStyle("blue"); //$NON-NLS-1$ Style red = doc.getStyle("red"); //$NON-NLS-1$ int beginVar = instructions.viewToModel(e.getPoint())-startVars; if(beginVar<0) { // mouse is over instructions doc.setCharacterAttributes(0, text.length(), blue, false); return; } while (beginVar>0) { // back up to preceding space String s = vars.substring(0, beginVar); if (s.endsWith(" ")) //$NON-NLS-1$ break; beginVar--; } varBegin = beginVar+startVars; // find following comma, space or end String s = vars.substring(beginVar); int len = s.indexOf(","); //$NON-NLS-1$ if(len==-1) len = s.indexOf(" "); //$NON-NLS-1$ if(len==-1) len = s.length(); // set variable bounds and character style varEnd = varBegin+len; doc.setCharacterAttributes(0, varBegin, blue, false); doc.setCharacterAttributes(varBegin, len, red, false); doc.setCharacterAttributes(varEnd, text.length()-varEnd, blue, false); } }); // create box and function editors box = Box.createVerticalBox(); add(box, BorderLayout.CENTER); if (functionEditor instanceof DataFunctionEditor) { DataFunctionEditor dfEditor = (DataFunctionEditor)functionEditor; paramEditor = new ParamEditor(dfEditor.getData()); } else paramEditor = new ParamEditor(); paramEditor.functionPanel = this; functionEditor.setParamEditor(paramEditor); paramEditor.setFunctionEditors(new FunctionEditor[] {functionEditor}); box.add(paramEditor); box.add(functionEditor); paramEditor.addPropertyChangeListener(this); paramEditor.addPropertyChangeListener(functionEditor); functionEditor.addPropertyChangeListener(this); functionEditor.addPropertyChangeListener(paramEditor); JScrollPane scroller = new JScrollPane(instructions) { public Dimension getPreferredSize() { Dimension dim = super.getPreferredSize(); Font font = instructions.getFont(); dim.height = Math.max(dim.height, font.getSize()*4); return dim; } }; box.add(scroller); // set up the undo system undoManager = new UndoManager(); undoSupport = new UndoableEditSupport(); undoSupport.addUndoableEditListener(undoManager); undoButton = new JButton(); undoButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { undoManager.undo(); } }); redoButton = new JButton(); redoButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { undoManager.redo(); } }); } /** * Refreshes the GUI. */ protected void refreshGUI() { undoButton.setText(ToolsRes.getString("DataFunctionPanel.Button.Undo")); //$NON-NLS-1$ undoButton.setToolTipText(ToolsRes.getString("DataFunctionPanel.Button.Undo.Tooltip")); //$NON-NLS-1$ redoButton.setText(ToolsRes.getString("DataFunctionPanel.Button.Redo")); //$NON-NLS-1$ redoButton.setToolTipText(ToolsRes.getString("DataFunctionPanel.Button.Redo.Tooltip")); //$NON-NLS-1$ undoButton.setEnabled(undoManager.canUndo()); redoButton.setEnabled(undoManager.canRedo()); if(functionTool!=null && functionTool.getSelectedPanel()==this) { boolean needsButtons = true; for (Component c: functionTool.buttonbar.getComponents()) { if (c == undoButton) needsButtons = false; } if (needsButtons) { functionTool.buttonbar.removeAll(); functionTool.buttonbar.add(functionTool.helpButton); functionTool.buttonbar.add(undoButton); functionTool.buttonbar.add(redoButton); functionTool.buttonbar.add(functionTool.fontButton); functionTool.buttonbar.add(functionTool.closeButton); } paramEditor.refreshGUI(); functionEditor.refreshGUI(); } // refreshInstructions(null, false, -1); } /** * Sets the font level. * * @param level the level */ protected void setFontLevel(int level) { FontSizer.setFonts(this, level); FontSizer.setFonts(undoButton, level); FontSizer.setFonts(redoButton, level); } /** * Gets the description for this panel. * * @return the description */ public String getDescription() { return description==null? "": description; //$NON-NLS-1$ } /** * Sets the description for this panel. * * @param desc the description */ public void setDescription(String desc) { description = desc; } /** * Gets the Icon for this panel, if any. * * @return the icon */ public Icon getIcon() { return icon; } /** * Sets the Icon for this panel. * * @param icon the icon */ public void setIcon(Icon icon) { this.icon = icon; } /** * Refreshes the functions. */ protected void refreshFunctions() { functionEditor.evaluateAll(); } /** * Sets the FunctionTool. This method is called by the tool to * which this panel is added. * * @param tool the FunctionTool */ public void setFunctionTool(FunctionTool tool) { functionTool = tool; } /** * Tabs to the next editor. * * @param editor the current editor */ protected void tabToNext(FunctionEditor editor) { if(editor==functionEditor) { functionTool.helpButton.requestFocusInWindow(); } else { functionEditor.newButton.requestFocusInWindow(); } } /** * Refreshes the instructions based on selected cell. * * @param source the function editor (may be null) * @param editing true if the table is editing * @param selectedColumn the selected table column, or -1 if none */ protected void refreshInstructions(FunctionEditor source, boolean editing, int selectedColumn) { StyledDocument doc = instructions.getStyledDocument(); Style style = doc.getStyle("blue"); //$NON-NLS-1$ String s = isEmpty() ? ToolsRes.getString("FunctionPanel.Instructions.GetStarted") //$NON-NLS-1$ : ToolsRes.getString("FunctionPanel.Instructions.General") //$NON-NLS-1$ +" "+ToolsRes.getString("FunctionPanel.Instructions.EditDescription"); //$NON-NLS-1$ //$NON-NLS-2$ if(!editing&&hasCircularErrors()) { // error condition s = ToolsRes.getString("FunctionPanel.Instructions.CircularErrors"); //$NON-NLS-1$ style = doc.getStyle("red"); //$NON-NLS-1$ } else if(!editing&&hasInvalidExpressions()) { // error condition s = ToolsRes.getString("FunctionPanel.Instructions.BadCell"); //$NON-NLS-1$ style = doc.getStyle("red"); //$NON-NLS-1$ } else if(source!=null) { if((selectedColumn==0)&&editing) { // editing name s = ToolsRes.getString("FunctionPanel.Instructions.NameCell"); //$NON-NLS-1$ } else if((selectedColumn==1)&&editing) { // editing expression s = source.getVariablesString(": "); //$NON-NLS-1$ } else if(selectedColumn>-1) { s = ToolsRes.getString("FunctionPanel.Instructions.EditCell"); //$NON-NLS-1$ if(selectedColumn==0) { s += " "+ToolsRes.getString("FunctionPanel.Instructions.NameCell"); //$NON-NLS-1$//$NON-NLS-2$ s += "\n"+ToolsRes.getString("FunctionPanel.Instructions.EditDescription"); //$NON-NLS-1$//$NON-NLS-2$ } else { s += " "+ToolsRes.getString("FunctionPanel.Instructions.Help"); //$NON-NLS-1$//$NON-NLS-2$ } } } instructions.setText(s); int len = instructions.getText().length(); doc.setCharacterAttributes(0, len, style, false); revalidate(); } protected boolean isEmpty() { return(functionEditor.getObjects().size()==0)&&(paramEditor.getObjects().size()==0); } protected boolean hasInvalidExpressions() { return functionEditor.containsInvalidExpressions()||paramEditor.containsInvalidExpressions(); } protected boolean hasCircularErrors() { return !(functionEditor.circularErrors.isEmpty()&¶mEditor.circularErrors.isEmpty()); } /** * Disposes of this panel. */ protected void dispose() { paramEditor.removePropertyChangeListener(this); paramEditor.removePropertyChangeListener(functionEditor); functionEditor.removePropertyChangeListener(this); functionEditor.removePropertyChangeListener(paramEditor); functionEditor.setParamEditor(null); paramEditor.setFunctionEditors(new FunctionEditor[0]); functionEditor.setFunctionPanel(null); functionEditor = null; paramEditor.setFunctionPanel(null); paramEditor = null; setFunctionTool(null); } @Override public void finalize() { OSPLog.finer(getClass().getSimpleName()+" resources released by garbage collector"); //$NON-NLS-1$ } } /* * 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 */