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