/*
* 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.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.util.Enumeration;
import java.util.Iterator;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.tools.ArrayInspector;
/**
* This is a split pane view of an XML tree and its contents.
*
* @author Douglas Brown
*/
public class XMLTreePanel extends JPanel {
// instance fields
protected JLabel label;
protected JTextField input;
protected JTextPane xmlPane;
protected JTree tree;
protected JScrollPane treeScroller = new JScrollPane();
protected Icon valueIcon;
protected Icon inspectIcon;
protected Icon inspectFolderIcon;
protected Icon folderIcon;
protected XMLControl control;
protected XMLProperty property;
protected boolean editable;
protected JPopupMenu popup;
int maxStringLength = 24;
/**
* Contructs a tree panel with an XMLControl
*
* @param control the XMLControl
*/
public XMLTreePanel(XMLControl control) {
this(control, true);
}
/**
* Contructs a tree panel with an XMLControl
*
* @param control the XMLControl
* @param editable true to enable xml edits via the input field
*/
public XMLTreePanel(XMLControl control, boolean editable) {
super(new BorderLayout());
this.control = control;
this.editable = editable;
createGUI();
}
/**
* Refreshes the tree. Called after changing the control externally.
*/
public void refresh() {
XMLTreeNode root = createTree(control);
displayProperty(root, editable);
}
/**
* Gets the control displayed in the tree.
*/
public XMLControl getControl() {
return control;
}
/**
* Selects and returns the first node with the specified property name.
*
* @param propertyName the property name
* @return the selected node, or null if none found
*/
public XMLTreeNode setSelectedNode(String propertyName) {
XMLTreeNode root = (XMLTreeNode) tree.getModel().getRoot();
Enumeration<?> e = root.breadthFirstEnumeration();
while(e.hasMoreElements()) {
XMLTreeNode node = (XMLTreeNode) e.nextElement();
XMLProperty prop = node.getProperty();
if(prop.getPropertyName().equals(propertyName)) {
TreePath path = new TreePath(node.getPath());
tree.setSelectionPath(path);
tree.scrollPathToVisible(path);
showInspector(node);
return node;
}
}
return null;
}
/**
* Displays the property data for the specified node.
*
* @param node the XMLTreeNode
* @param editable <code>true</code> if the input field is editable
*/
protected void displayProperty(XMLTreeNode node, boolean editable) {
// input field hidden by default
input.setVisible(false);
XMLProperty prop = node.getProperty();
// display property type and name on label
label.setText(prop.getPropertyType()+" "+prop.getPropertyName()); //$NON-NLS-1$
if(!prop.getPropertyContent().isEmpty()) {
// get first content item
Object value = prop.getPropertyContent().get(0);
// display primitive properties in input field
if(value instanceof String) {
property = prop;
String content = (String) value;
if(content.indexOf(XML.CDATA_PRE)!=-1) {
content = content.substring(content.indexOf(XML.CDATA_PRE)+XML.CDATA_PRE.length(), content.length()-XML.CDATA_POST.length());
}
input.setText(content);
input.setEditable(editable);
input.setVisible(true);
}
}
// display xml in xmlPane
String xml = prop.toString();
xmlPane.setText(getDisplay(xml));
xmlPane.setCaretPosition(0);
}
/**
* Gets the xml to be displayed.
*
* @param xml the raw xml
* @return the displayed xml
*/
protected String getDisplay(String xml) {
// find and truncate every array string in the xml
String newXML = ""; // newly assembled xml //$NON-NLS-1$
String preArray = "name=\"array\" type=\"string\">"; //$NON-NLS-1$
String postArray = "</property>"; //$NON-NLS-1$
String array;
int i = xml.indexOf(preArray);
while(i>0) {
i += preArray.length();
newXML += xml.substring(0, i);
xml = xml.substring(i);
i = xml.indexOf(postArray);
array = xml.substring(0, i);
xml = xml.substring(i, xml.length());
if(array.length()>maxStringLength) {
array = array.substring(0, maxStringLength-3)+"..."; //$NON-NLS-1$
}
newXML += array;
i = xml.indexOf(preArray);
}
newXML += xml;
return newXML;
}
/**
* Creates the GUI and listeners.
*/
protected void createGUI() {
// create popup and icons
String imageFile = "/org/opensourcephysics/resources/controls/images/inspect.gif"; //$NON-NLS-1$
// Don't use resource loader to improve performance. Changed by W. Christian
//inspectIcon = ResourceLoader.getIcon(imageFile);
inspectIcon = new ImageIcon(XMLTreePanel.class.getResource(imageFile));
imageFile = "/org/opensourcephysics/resources/controls/images/value.gif"; //$NON-NLS-1$
//valueIcon = ResourceLoader.getIcon(imageFile);
valueIcon = new ImageIcon(XMLTreePanel.class.getResource(imageFile));
imageFile = "/org/opensourcephysics/resources/controls/images/folder.gif"; //$NON-NLS-1$
//folderIcon = ResourceLoader.getIcon(imageFile);
folderIcon = new ImageIcon(XMLTreePanel.class.getResource(imageFile));
imageFile = "/org/opensourcephysics/resources/controls/images/inspectfolder.gif"; //$NON-NLS-1$
//inspectFolderIcon = ResourceLoader.getIcon(imageFile);
inspectFolderIcon = new ImageIcon(XMLTreePanel.class.getResource(imageFile));
popup = new JPopupMenu();
JMenuItem item = new JMenuItem(ControlsRes.getString("XMLTreePanel.Popup.MenuItem.Inspect")); //$NON-NLS-1$
popup.add(item);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
XMLTreeNode node = (XMLTreeNode) tree.getLastSelectedPathComponent();
if(node!=null) {
showInspector(node);
}
}
});
// create tree
XMLTreeNode root = createTree(control);
// create toolbar for label and input text field
JToolBar toolbar = new JToolBar();
toolbar.setFloatable(false);
// create label
label = new JLabel();
toolbar.add(label);
// create input text field
input = new JTextField(20);
input.setVisible(false);
input.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
property.setValue(input.getText());
Object obj = control.loadObject(null);
if(obj instanceof Component) {
((Component) obj).repaint();
}
input.setText((String) property.getPropertyContent().get(0));
input.selectAll();
XMLTreeNode node = (XMLTreeNode) tree.getLastSelectedPathComponent();
if(node!=null) {
displayProperty(node, editable);
}
}
});
input.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(!editable) {
return;
}
JComponent comp = (JComponent) e.getSource();
if(e.getKeyCode()==KeyEvent.VK_ENTER) {
comp.setBackground(Color.white);
} else {
comp.setBackground(Color.yellow);
}
}
});
input.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) {
JComponent comp = (JComponent) e.getSource();
comp.setBackground(Color.white);
}
});
toolbar.add(input);
// create xml pane and scroller
xmlPane = 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);
}
};
xmlPane.setPreferredSize(new Dimension(360, 200));
xmlPane.setEditable(false);
JScrollPane xmlScroller = new JScrollPane(xmlPane);
// create data panel for right side of split pane
JPanel dataPanel = new JPanel(new BorderLayout());
dataPanel.add(toolbar, BorderLayout.NORTH);
dataPanel.add(xmlScroller, BorderLayout.CENTER);
// create split pane
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeScroller, dataPanel);
add(splitPane, BorderLayout.CENTER);
treeScroller.setPreferredSize(new Dimension(140, 200));
displayProperty(root, editable);
}
private XMLTreeNode createTree(XMLControl control) {
XMLTreeNode root = new XMLTreeNode(control);
tree = new JTree(root);
tree.setCellRenderer(new XMLRenderer());
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
// listen for tree selections and display the property data
tree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
XMLTreeNode node = (XMLTreeNode) tree.getLastSelectedPathComponent();
if(node!=null) {
displayProperty(node, editable);
}
}
});
// listen for mouse events to display array tables
tree.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if(OSPRuntime.isPopupTrigger(e)) {
// select node and show popup menu
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if(path==null) {
return;
}
tree.setSelectionPath(path);
XMLTreeNode node = (XMLTreeNode) tree.getLastSelectedPathComponent();
if(node.isInspectable()) {
popup.show(tree, e.getX(), e.getY()+8);
}
}
}
});
// put tree in scroller
treeScroller.setViewportView(tree);
return root;
}
private void showInspector(XMLTreeNode node) {
if(node==null) {
return;
}
// show array inspector if available
if(node.getProperty().getPropertyType().equals("array")) { //$NON-NLS-1$
XMLProperty arrayProp = node.getProperty();
ArrayInspector inspector = ArrayInspector.getInspector(arrayProp);
if(inspector!=null) {
String name = arrayProp.getPropertyName();
XMLProperty parent = arrayProp.getParentProperty();
while(!(parent instanceof XMLControl)) {
name = parent.getPropertyName();
arrayProp = parent;
parent = parent.getParentProperty();
}
final XMLControl arrayControl = (XMLControl) parent;
final String arrayName = name;
final Object arrayObj = inspector.getArray();
final XMLTreeNode parentNode = (XMLTreeNode) node.getParent();
inspector.setEditable(editable);
// listen for changes in the table array
inspector.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if(e.getPropertyName().equals("cell")) { //$NON-NLS-1$
// set new array value in array control (creates new XMLProperty)
arrayControl.setValue(arrayName, arrayObj);
control.loadObject(null);
// find the new XMLProperty and make a new tree node for it
Iterator<?> it = arrayControl.getPropertyContent().iterator();
while(it.hasNext()) {
XMLProperty next = (XMLProperty) it.next();
if(next.getPropertyName().equals(arrayName)) {
// replace current tree node with new one
for(int i = 0; i<parentNode.getChildCount(); i++) {
XMLTreeNode node = (XMLTreeNode) parentNode.getChildAt(i);
if(node.getProperty().getPropertyName().equals(arrayName)) {
XMLTreeNode child = new XMLTreeNode(next);
TreeModel model = tree.getModel();
if(model instanceof DefaultTreeModel) {
DefaultTreeModel treeModel = (DefaultTreeModel) model;
treeModel.removeNodeFromParent(node);
treeModel.insertNodeInto(child, parentNode, i);
TreePath path = new TreePath(child.getPath());
tree.setSelectionPath(path);
}
break;
}
}
break;
}
}
}
}
});
// offset new inspector relative to parent container
Container cont = getTopLevelAncestor();
Point p = cont.getLocationOnScreen();
inspector.setLocation(p.x+30, p.y+30);
inspector.setVisible(true);
}
}
}
/**
* A cell renderer to show xml nodes.
*/
private class XMLRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
XMLTreeNode node = (XMLTreeNode) value;
if(node.isLeaf()) {
if(node.isInspectable()) {
setIcon(inspectIcon);
} else {
setIcon(valueIcon);
}
} else if(node.isInspectable()) {
setIcon(inspectFolderIcon);
} else {
setIcon(folderIcon);
}
return this;
}
}
}
/*
* 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
*/