/*
 * 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.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Toolkit;
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.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.controls.XMLControlElement;
import org.opensourcephysics.controls.XMLTreePanel;

/**
 * This is a dialog that displays a tree showing tabs to be saved.
 *
 * @author Douglas Brown
 * @version 1.0
 */
public class LaunchSaver extends JDialog {
  // instance fields
  private String prevTabSetName;
  private String prevTabSetBasePath;
  private boolean prevTabSetSelfContained;
  private Map<Node, String> prevNodeNames = new HashMap<Node, String>();
  private Map<Node, Boolean> prevNodeSelfContains = new HashMap<Node, Boolean>();
  private LaunchBuilder builder;
  private DefaultTreeModel treeModel;
  private JTree tree;
  private ArrayList<TreePath> treePaths = new ArrayList<TreePath>();
  private JTextField pathField;
  protected JCheckBox saveStateCheckBox;
  private JButton chooseButton;
  private JButton inspectButton;
  private Node root;
  private JScrollPane treeScroller;
  private boolean approved = false;
  private Editor editor = new Editor();
  private JDialog inspector;
  private boolean active;

  /**
   * Constructor.
   *
   * @param builder the LaunchBuilder with tabs to save
   */
  public LaunchSaver(LaunchBuilder builder) {
    super(builder.frame, true);
    createGUI();
    setBuilder(builder); // creates tree for launch builder
    if(FontSizer.getLevel()!=0) {
      FontSizer.setFonts(getContentPane(), FontSizer.getLevel());
    }
    pack();
    // center on screen
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    int x = (dim.width-getBounds().width)/2;
    int y = (dim.height-getBounds().height)/2;
    setLocation(x, y);
  }

  /**
   * Sets the LaunchBuilder and creates a new tree.
   *
   * @param builder the LaunchBuilder
   */
  public void setBuilder(LaunchBuilder builder) {
    this.builder = builder;
    createTree();
    refresh();
    approved = false;
  }

  /**
   * Sets the selected node.
   *
   * @param node the node to select
   */
  public void setSelectedNode(Node node) {
    if(node==null) {
      return;
    }
    tree.setSelectionPath(new TreePath(node.getPath()));
  }

  /**
   * Gets the selected node.
   *
   * @return the selected node
   */
  public Node getSelectedNode() {
    TreePath path = tree.getSelectionPath();
    if(path==null) {
      return null;
    }
    return(Node) path.getLastPathComponent();
  }

  /**
   * Returns true if user has clicked the save button, otherwise false.
   *
   * @return true to save
   */
  public boolean isApproved() {
    return approved;
  }

  // ______________________________ private methods _____________________________

  /**
   * Refreshes the GUI.
   */
  private void refresh() {
    inspectButton.setEnabled(getSelectedNode()!=null);
    String path = null;
    if(!Launcher.tabSetBasePath.equals("")) { //$NON-NLS-1$
      path = Launcher.tabSetBasePath;
    } else if(builder.jarBasePath!=null) {
      path = builder.jarBasePath;
    } else {
      path = XML.forwardSlash(XML.getUserDirectory());
    }
    if(path.indexOf("javaws")>-1) {                                  //$NON-NLS-1$
      path = XML.forwardSlash(System.getProperty("user.home", ".")); //$NON-NLS-1$ //$NON-NLS-2$
      Launcher.tabSetBasePath = path;
    }
    pathField.setText(path+"/"); //$NON-NLS-1$
    pathField.setBackground(Color.white);
    treeModel.nodeChanged(root);
    tree.repaint();
  }

  /**
   * Displays the properties of the selected node in an xml inspector.
   */
  private void inspectSelectedNode() {
    Node node = getSelectedNode();
    if(node==null) {
      node = root;
    }
    Object obj = node.node;
    if(obj==null) {
      obj = builder.new LaunchSet(builder, builder.tabSetName);
    }
    XMLControl xml = new XMLControlElement(obj);
    XMLTreePanel treePanel = new XMLTreePanel(xml, false);
    inspector.setContentPane(treePanel);
    String name = (node!=root) ? node.node.getFileName() : builder.tabSetName;
    name = XML.getResolvedPath(name, Launcher.tabSetBasePath);
    inspector.setTitle(LaunchRes.getString("Inspector.Title.File")+" \""+name+"\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    inspector.setVisible(true);
  }

  /**
   * Reverts to initial state.
   */
  private void revert() {
    Launcher.tabSetBasePath = prevTabSetBasePath;
    builder.tabSetName = prevTabSetName;
    builder.selfContained = prevTabSetSelfContained;
    // set parentSelfContained property of tabs
    for(int i = 0; i<builder.tabbedPane.getTabCount(); i++) {
      LaunchNode root = builder.getTab(i).getRootNode();
      root.parentSelfContained = prevTabSetSelfContained;
    }
    Iterator<Node> it = prevNodeNames.keySet().iterator();
    while(it.hasNext()) {
      Node node = it.next();
      String path = prevNodeNames.get(node);
      node.node.setFileName(path);
      Boolean bool = prevNodeSelfContains.get(node);
      node.node.setSelfContained(bool.booleanValue());
    }
  }

  /**
   * Sets the base path.
   *
   * @param path the path
   */
  private void setTabSetBasePath(String path) {
    path = XML.forwardSlash(path);
    // strip leading and trailing slashes
    boolean leadingSlash = false;
    while(path.startsWith("/")) { //$NON-NLS-1$
      leadingSlash = true;
      path = path.substring(1);
    }
    // replace single leading slash if any found
    if(leadingSlash) {
      path = "/"+path; //$NON-NLS-1$
    }
    while(path.endsWith("/")) { //$NON-NLS-1$
      path = path.substring(0, path.length()-1);
    }
    if(!path.startsWith("/")&&(path.indexOf(":")==-1)) { // not an absolute path //$NON-NLS-1$ //$NON-NLS-2$
      String base = XML.forwardSlash(XML.getUserDirectory());
      if(builder.jarBasePath!=null) {
        base = builder.jarBasePath;
      }
      if(!path.equals("")) {                             //$NON-NLS-1$
        base += "/";                                     //$NON-NLS-1$
      }
      path = base+path;
    }
    Launcher.tabSetBasePath = path;
    refresh();
  }

  /**
   * Sets the tabset name.
   *
   * @param proposed the proposed name
   * @return the tabset name
   */
  private String setTabSetName(String proposed) {
    String name = XML.getName(proposed);
    if(!"xset".equals(XML.getExtension(name))) { //$NON-NLS-1$
      while(name.indexOf(".")>-1) {              //$NON-NLS-1$
        name = name.substring(0, name.length()-1);
      }
      name += ".xset";                           //$NON-NLS-1$
    }
    builder.tabSetName = name;
    refresh();
    return name;
  }

  /**
   * Creates the GUI.
   */
  private void createGUI() {
    // create xml inspector
    inspector = new JDialog(this, false);
    inspector.setSize(new java.awt.Dimension(600, 300));
    // center inspector on screen
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    int x = (dim.width-inspector.getBounds().width)/2;
    int y = (dim.height-inspector.getBounds().height)/2;
    inspector.setLocation(x, y);
    // create panel components
    setTitle(LaunchRes.getString("Saver.Title")); //$NON-NLS-1$
    JPanel panel = new JPanel(new BorderLayout());
    panel.setPreferredSize(new Dimension(600, 300));
    setContentPane(panel);
    JPanel legend = new JPanel(new GridLayout(1, 4));
    panel.add(legend, BorderLayout.NORTH);
    JLabel label = new JLabel(LaunchRes.getString("Saver.Legend.New"), Launcher.greenFileIcon, SwingConstants.HORIZONTAL); //$NON-NLS-1$
    label.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
    legend.add(label);
    label = new JLabel(LaunchRes.getString("Saver.Legend.Replace"), Launcher.yellowFileIcon, SwingConstants.HORIZONTAL); //$NON-NLS-1$
    label.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
    legend.add(label);
    label = new JLabel(LaunchRes.getString("Saver.Legend.ReadOnly"), Launcher.redFileIcon, SwingConstants.HORIZONTAL); //$NON-NLS-1$
    label.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
    legend.add(label);
    label = new JLabel(LaunchRes.getString("Saver.Legend.SelfContained"), Launcher.whiteFolderIcon, SwingConstants.HORIZONTAL); //$NON-NLS-1$
    label.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
    legend.add(label);
    legend.setBorder(BorderFactory.createLoweredBevelBorder());
    JPanel lower = new JPanel(new BorderLayout());
    panel.add(lower, BorderLayout.SOUTH);
    JToolBar toolbar = new JToolBar();
    toolbar.setFloatable(false);
    lower.add(toolbar, BorderLayout.NORTH);
    label = new JLabel(LaunchRes.getString("Saver.Label.Base")); //$NON-NLS-1$
    label.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 4));
    toolbar.add(label);
    pathField = new JTextField();
    pathField.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e) {
        if(e.getKeyCode()==KeyEvent.VK_ENTER) {
          setTabSetBasePath(pathField.getText());
        } else {
          pathField.setBackground(Color.yellow);
        }
      }

    });
    pathField.addFocusListener(new FocusAdapter() {
      public void focusLost(FocusEvent e) {
        setTabSetBasePath(pathField.getText());
      }

    });
    toolbar.add(pathField);
    // choose button
    chooseButton = new JButton(LaunchRes.getString("Saver.Button.Choose")); //$NON-NLS-1$
    chooseButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        chooseTabSet();
      }

    });
    // inspect button
    inspectButton = new JButton(LaunchRes.getString("MenuItem.Inspect")); //$NON-NLS-1$
    inspectButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        inspectSelectedNode();
      }

    });
    // saveState checkbox
    saveStateCheckBox = new JCheckBox(LaunchRes.getString("Saver.Checkbox.SaveState")); //$NON-NLS-1$
    saveStateCheckBox.setOpaque(false);
    JToolBar buttonbar = new JToolBar();
    buttonbar.setFloatable(false);
    buttonbar.add(Box.createHorizontalGlue());
    buttonbar.add(Box.createHorizontalGlue());
    buttonbar.add(Box.createHorizontalGlue());
    JPanel buttons = new JPanel();
    buttons.setOpaque(false);
    buttonbar.add(buttons);
    buttons.add(saveStateCheckBox);
    buttons.add(inspectButton);
    buttons.add(chooseButton);
    lower.add(buttonbar, BorderLayout.SOUTH);
    // save button
    JButton saveButton = new JButton(LaunchRes.getString("Saver.Button.Save")); //$NON-NLS-1$
    buttons.add(saveButton);
    saveButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        approved = true;
        setVisible(false);
      }

    });
    // cancel button
    JButton cancelButton = new JButton(LaunchRes.getString("Saver.Button.Cancel")); //$NON-NLS-1$
    buttons.add(cancelButton);
    cancelButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        revert();
        setVisible(false);
      }

    });
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        if(!isApproved()) {
          revert();
        }
      }

    });
  }

  /**
   * Creates a new tree.
   */
  private void createTree() {
    active = false;
    treePaths.clear();
    // save initial values for revert method
    prevNodeNames.clear();
    prevNodeSelfContains.clear();
    prevTabSetName = builder.tabSetName;
    prevTabSetBasePath = Launcher.tabSetBasePath;
    prevTabSetSelfContained = builder.selfContained;
    // create root node
    root = new Node();
    treePaths.add(new TreePath(root.getPath()));
    // add child nodes to root
    int n = builder.tabbedPane.getTabCount();
    for(int i = 0; i<n; i++) {
      Node tab = new Node(builder.getTab(i).getRootNode());
      if(tab.node.getFileName()!=null) {
        root.add(tab);
        addChildren(tab); // also saves tab file name and selfContained
      } else {
        LaunchNode[] nodes = tab.node.getChildOwnedNodes();
        for(int k = 0; k<nodes.length; k++) {
          Node node = new Node(nodes[k]);
          root.add(node);
          addChildren(node);
        }
      }
    }
    // create tree model and tree
    treeModel = new DefaultTreeModel(root);
    tree = new JTree(treeModel);
    tree.setCellRenderer(new Renderer());
    tree.setCellEditor(editor);
    tree.setEditable(true);
    tree.setShowsRootHandles(true);
    tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
    tree.addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        TreePath path = tree.getPathForLocation(e.getX(), e.getY());
        if(path==null) {
          editor.stopCellEditing();
        }
      }

    });
    tree.addTreeWillExpandListener(new ExpansionListener());
    tree.addTreeSelectionListener(new TreeSelectionListener() {
      public void valueChanged(TreeSelectionEvent e) {
        if(inspector.isVisible()) {
          inspectSelectedNode();
        }
      }

    });
    // set tree row height
    FontRenderContext frc = new FontRenderContext(null, // no AffineTransform
      false,                                            // no antialiasing
        false);                                         // no fractional metrics
    Font font = editor.field.getFont();
    Rectangle2D rect = font.getStringBounds("A", frc);  //$NON-NLS-1$
    int h = (int) rect.getHeight();
    tree.setRowHeight(h+3);
    tree.setBorder(BorderFactory.createEmptyBorder(4, 0, 2, 0));
    // put tree in a scroller and add to dialog
    if(treeScroller!=null) {
      getContentPane().remove(treeScroller);
    }
    treeScroller = new JScrollPane(tree);
    getContentPane().add(treeScroller, BorderLayout.CENTER);
    setSelectedNode(root);
    // collapse tree to show self-containment structure
    ListIterator<TreePath> it = treePaths.listIterator();
    while(it.hasNext()) {
      TreePath path = it.next();
      tree.expandPath(path);
    }
    while(it.hasPrevious()) {
      TreePath path = it.previous();
      Node node = (Node) path.getLastPathComponent();
      if(isFolder(node)) {
        tree.collapsePath(path);
      }
    }
    active = true;
  }

  private void addChildren(Node node) {
    // save tab file name and selfContained property
    prevNodeNames.put(node, node.node.getFileName());
    prevNodeSelfContains.put(node, new Boolean(node.node.selfContained));
    treePaths.add(new TreePath(node.getPath()));
    // add node's owned child nodes
    LaunchNode[] nodes = node.node.getChildOwnedNodes();
    for(int k = 0; k<nodes.length; k++) {
      Node child = new Node(nodes[k]);
      node.add(child);
      addChildren(child);
    }
  }

  private boolean isFolder(Node node) {
    if(node==root) {
      return builder.selfContained;
    }
    return node.node.selfContained&&!node.isLeaf();
  }

  /**
   * Selects the tabset with a chooser.
   */
  private void chooseTabSet() {
    String fileName = XML.getResolvedPath(builder.tabSetName, Launcher.tabSetBasePath);
    JFileChooser chooser = Launcher.getXMLChooser();
    chooser.setSelectedFile(new File(fileName));
    chooser.setFileFilter(Launcher.xsetFileFilter);
    int result = chooser.showDialog(this, LaunchRes.getString("Saver.FileChooser.Title")); //$NON-NLS-1$
    if(result==JFileChooser.APPROVE_OPTION) {
      File file = chooser.getSelectedFile();
      fileName = file.getAbsolutePath();
      setTabSetName(fileName);
      setTabSetBasePath(XML.getDirectoryPath(fileName));
    }
    refresh();
  }

  private Icon getIcon(Node node) {
    File file;
    if(node==root) {
      String path = XML.getResolvedPath(builder.tabSetName, Launcher.tabSetBasePath);
      Resource res = ResourceLoader.getResource(path);
      file = ((res==null) ? null : res.getFile());
    } else {
      file = node.node.getFile();
    }
    // green icon if creating
    if(file==null) {
      if(isFolder(node)) {
        return Launcher.greenFolderIcon;
      }
      return Launcher.greenFileIcon;
    }
    // yellow icon if replacing
    else if(file.canWrite()) {
      if(isFolder(node)) {
        return Launcher.yellowFolderIcon;
      }
      return Launcher.yellowFileIcon;
    }
    // red icon if read-only
    else {
      if(isFolder(node)) {
        return Launcher.redFolderIcon;
      }
      return Launcher.redFileIcon;
    }
  }

  /**
   * A tree node.
   */
  private class Node extends DefaultMutableTreeNode {
    private LaunchNode node;

    // Constructor.
    Node() {
      /** empty block */
    }

    // Constructor for launch node.
    Node(LaunchNode node) {
      this.node = node;
    }

    // returns the text displayed.
    public String toString() {
      if(node==null) {
        return LaunchRes.getString("Saver.Tree.TabSet")+":";                 //$NON-NLS-1$ //$NON-NLS-2$
      } else if(node.isRoot()) {
        return LaunchRes.getString("Saver.Tree.Tab")+" \""+node.name+"\":";  //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      } else {
        return LaunchRes.getString("Saver.Tree.Node")+" \""+node.name+"\":"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      }
    }

  }

  /**
   * A class to render tree cells.
   */
  class Renderer implements TreeCellRenderer {
    JPanel panel = new JPanel(new BorderLayout());
    JLabel label = new JLabel();
    JTextField field = new JTextField();

    // Constructor.

    /**
     * Constructor Renderer
     */
    public Renderer() {
      panel.add(label, BorderLayout.WEST);
      panel.add(field, BorderLayout.CENTER);
      panel.setOpaque(false);
      label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
      field.setBorder(null);
      field.setBackground(field.getSelectionColor());
    }

    // Gets the component to be displayed.
    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
      Node node = (Node) value;
      String name = (node!=root) ? node.node.getFileName() : builder.tabSetName;
      field.setText(name);
      label.setIcon(getIcon(node));
      label.setText(node.toString());
      if(FontSizer.getLevel()!=0) {
        FontSizer.setFonts(panel, FontSizer.getLevel());
      }
      field.setOpaque(selected ? true : false);
      return panel;
    }

  }

  /**
   * A class to edit tree node names.
   */
  private class Editor extends AbstractCellEditor implements TreeCellEditor {
    JPanel panel = new JPanel(new BorderLayout());
    JTextField field = new JTextField();
    JLabel label = new JLabel();

    // Constructor.
    Editor() {
      panel.add(label, BorderLayout.WEST);
      panel.add(field, BorderLayout.CENTER);
      panel.setOpaque(false);
      label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4));
      field.setBorder(BorderFactory.createLineBorder(Color.black));
      field.setEditable(true);
      field.setColumns(30);
      field.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          stopCellEditing();
        }

      });
      field.addKeyListener(new KeyAdapter() {
        public void keyPressed(KeyEvent e) {
          if(e.getKeyCode()==KeyEvent.VK_ENTER) {
            stopCellEditing();
          } else {
            field.setBackground(Color.yellow);
          }
        }

      });
      field.addFocusListener(new FocusAdapter() {
        public void focusLost(FocusEvent e) {
          stopCellEditing();
        }

      });
    }

    // Gets the component to be displayed while editing.
    public Component getTreeCellEditorComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row) {
      Node node = (Node) value;
      String name = (node!=root) ? node.node.getFileName() : builder.tabSetName;
      field.setText(name);
      label.setIcon(getIcon(node));
      label.setText(node.toString());
      if(FontSizer.getLevel()!=0) {
        FontSizer.setFonts(panel, FontSizer.getLevel());
      }
      return panel;
    }

    // Determines when editing starts.
    public boolean isCellEditable(EventObject e) {
      if(e instanceof MouseEvent) {
        return true;
      }
      return false;
    }

    // Called when editing is completed.
    public Object getCellEditorValue() {
      field.setBackground(Color.white);
      Node node = getSelectedNode();
      String name = field.getText();
      // prevent duplicate file names
      String prev;
      if(node.node!=null) {
        prev = node.node.getFileName();
      } else {
        prev = builder.tabSetName;
      }
      if(builder.getOpenPaths().contains(name)&&!prev.equals(name)) {
        JOptionPane.showMessageDialog(LaunchSaver.this, LaunchRes.getString("Dialog.DuplicateFileName.Message")+" \""+name+"\"", LaunchRes.getString("Dialog.DuplicateFileName.Title"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
        refresh();
        return null;
      }
      if(node.node!=null) {
        node.node.setFileName(name);
      } else {
        name = setTabSetName(name);
      }
      return name;
    }

  }

  private class ExpansionListener implements TreeWillExpandListener {
    public void treeWillExpand(TreeExpansionEvent e) {
      TreePath path = e.getPath();
      set(path, false);
    }

    public void treeWillCollapse(TreeExpansionEvent e) {
      TreePath path = e.getPath();
      set(path, true);
    }

    private void set(TreePath path, boolean selfContained) {
      if(active) {
        Node node = (Node) path.getLastPathComponent();
        if(node==root) {
          builder.selfContained = selfContained;
          // set parentSelfContained property of tabs
          for(int i = 0; i<builder.tabbedPane.getTabCount(); i++) {
            LaunchNode root = builder.getTab(i).getRootNode();
            root.parentSelfContained = selfContained;
          }
        } else {
          node.node.setSelfContained(selfContained);
        }
        if(inspector.isVisible()) {
          inspectSelectedNode();
        }
      }
    }

  }

}

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